From 9ae96e98ca8ddd9873e8626ac3b6b759f7fc1f5c Mon Sep 17 00:00:00 2001 From: attila Date: Tue, 1 Mar 2022 16:19:16 +0100 Subject: [PATCH] ARA Client: Add ARA plugin model classes for writing plugins --- .../format/juce_ARAAudioReaders.cpp | 303 +++++ .../format/juce_ARAAudioReaders.h | 187 +++ .../juce_audio_formats/juce_audio_formats.cpp | 5 + .../juce_audio_formats/juce_audio_formats.h | 6 + .../juce_audio_processors.cpp | 1 + .../juce_audio_processors.h | 1 + .../juce_audio_processors_ara.cpp | 2 +- .../ARA/juce_ARADocumentController.cpp | 964 ++++++++++++++ .../ARA/juce_ARADocumentController.h | 513 ++++++++ .../ARA/juce_ARADocumentControllerCommon.cpp | 64 + .../utilities/ARA/juce_ARAModelObjects.cpp | 192 +++ .../utilities/ARA/juce_ARAModelObjects.h | 1138 +++++++++++++++++ .../ARA/juce_ARAPlugInInstanceRoles.cpp | 85 ++ .../ARA/juce_ARAPlugInInstanceRoles.h | 246 ++++ .../utilities/ARA/juce_ARA_utils.cpp | 25 + .../utilities/ARA/juce_ARA_utils.h | 78 ++ .../ARA/juce_AudioProcessor_ARAExtensions.cpp | 149 +++ .../ARA/juce_AudioProcessor_ARAExtensions.h | 179 +++ 18 files changed, 4137 insertions(+), 1 deletion(-) create mode 100644 modules/juce_audio_formats/format/juce_ARAAudioReaders.cpp create mode 100644 modules/juce_audio_formats/format/juce_ARAAudioReaders.h create mode 100644 modules/juce_audio_processors/utilities/ARA/juce_ARADocumentController.cpp create mode 100644 modules/juce_audio_processors/utilities/ARA/juce_ARADocumentController.h create mode 100644 modules/juce_audio_processors/utilities/ARA/juce_ARADocumentControllerCommon.cpp create mode 100644 modules/juce_audio_processors/utilities/ARA/juce_ARAModelObjects.cpp create mode 100644 modules/juce_audio_processors/utilities/ARA/juce_ARAModelObjects.h create mode 100644 modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.cpp create mode 100644 modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.h create mode 100644 modules/juce_audio_processors/utilities/ARA/juce_ARA_utils.cpp create mode 100644 modules/juce_audio_processors/utilities/ARA/juce_ARA_utils.h create mode 100644 modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.cpp create mode 100644 modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.h diff --git a/modules/juce_audio_formats/format/juce_ARAAudioReaders.cpp b/modules/juce_audio_formats/format/juce_ARAAudioReaders.cpp new file mode 100644 index 0000000000..026fe492c0 --- /dev/null +++ b/modules/juce_audio_formats/format/juce_ARAAudioReaders.cpp @@ -0,0 +1,303 @@ +/* + ============================================================================== + + This file is part of the JUCE 7 technical preview. + Copyright (c) 2022 - Raw Material Software Limited + + You may use this code under the terms of the GPL v3 + (see www.gnu.org/licenses). + + For the technical preview this file cannot be licensed commercially. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +ARAAudioSourceReader::ARAAudioSourceReader (ARAAudioSource* audioSource) + : AudioFormatReader (nullptr, "ARAAudioSourceReader"), + audioSourceBeingRead (audioSource) +{ + jassert (audioSourceBeingRead != nullptr); + + bitsPerSample = 32; + usesFloatingPointData = true; + sampleRate = audioSourceBeingRead->getSampleRate(); + numChannels = (unsigned int) audioSourceBeingRead->getChannelCount(); + lengthInSamples = audioSourceBeingRead->getSampleCount(); + tmpPtrs.resize (numChannels); + + audioSourceBeingRead->addListener (this); + + if (audioSourceBeingRead->isSampleAccessEnabled()) + hostReader.reset (new ARA::PlugIn::HostAudioReader (audioSourceBeingRead)); +} + +ARAAudioSourceReader::~ARAAudioSourceReader() +{ + invalidate(); +} + +void ARAAudioSourceReader::invalidate() +{ + ScopedWriteLock scopedLock (lock); + + if (! isValid()) + return; + + hostReader.reset(); + + audioSourceBeingRead->removeListener (this); + audioSourceBeingRead = nullptr; +} + +void ARAAudioSourceReader::willUpdateAudioSourceProperties (ARAAudioSource* audioSource, + ARAAudioSource::PropertiesPtr newProperties) +{ + if (audioSource->getSampleCount() != newProperties->sampleCount + || audioSource->getSampleRate() != newProperties->sampleRate + || audioSource->getChannelCount() != newProperties->channelCount) + { + invalidate(); + } +} + +void ARAAudioSourceReader::doUpdateAudioSourceContent (ARAAudioSource* audioSource, + ARAContentUpdateScopes scopeFlags) +{ + jassertquiet (audioSourceBeingRead == audioSource); + + // Don't invalidate if the audio signal is unchanged + if (scopeFlags.affectSamples()) + invalidate(); +} + +void ARAAudioSourceReader::willEnableAudioSourceSamplesAccess (ARAAudioSource* audioSource, bool enable) +{ + jassertquiet (audioSourceBeingRead == audioSource); + + // Invalidate our reader if sample access is disabled + if (! enable) + { + ScopedWriteLock scopedLock (lock); + hostReader.reset(); + } +} + +void ARAAudioSourceReader::didEnableAudioSourceSamplesAccess (ARAAudioSource* audioSource, bool enable) +{ + jassertquiet (audioSourceBeingRead == audioSource); + + // Recreate our reader if sample access is enabled + if (enable && isValid()) + { + ScopedWriteLock scopedLock (lock); + hostReader.reset (new ARA::PlugIn::HostAudioReader (audioSourceBeingRead)); + } +} + +void ARAAudioSourceReader::willDestroyAudioSource (ARAAudioSource* audioSource) +{ + jassertquiet (audioSourceBeingRead == audioSource); + + invalidate(); +} + +bool ARAAudioSourceReader::readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, + int64 startSampleInFile, int numSamples) +{ + const auto destSize = (bitsPerSample / 8) * (size_t) numSamples; + const auto bufferOffset = (int) (bitsPerSample / 8) * startOffsetInDestBuffer; + + if (isValid() && hostReader != nullptr) + { + const ScopedTryReadLock readLock (lock); + + if (readLock.isLocked()) + { + for (size_t i = 0; i < tmpPtrs.size(); ++i) + { + if ((i < (size_t) numDestChannels) && (destSamples[i] != nullptr)) + { + tmpPtrs[i] = ((uint8_t*) destSamples[i]) + bufferOffset; + } + else + { + // We need to provide destination pointers for all channels in the ARA read call, even if + // readSamples is not reading all of them. Hence we use this dummy buffer to pad the read + // destination area. + static thread_local std::vector dummyBuffer; + + if (destSize > dummyBuffer.size()) + dummyBuffer.resize (destSize); + + tmpPtrs[i] = dummyBuffer.data(); + } + } + + return hostReader->readAudioSamples (startSampleInFile, numSamples, tmpPtrs.data()); + } + } + + for (int i = 0; i < numDestChannels; ++i) + if (destSamples[i] != nullptr) + zeromem (((uint8_t*) destSamples[i]) + bufferOffset, destSize); + + return false; +} + +//============================================================================== +ARAPlaybackRegionReader::ARAPlaybackRegionReader (ARAPlaybackRegion* playbackRegion) + : ARAPlaybackRegionReader (playbackRegion->getAudioModification()->getAudioSource()->getSampleRate(), + playbackRegion->getAudioModification()->getAudioSource()->getChannelCount(), + { playbackRegion }) +{} + +ARAPlaybackRegionReader::ARAPlaybackRegionReader (double rate, int numChans, + const std::vector& playbackRegions) + : AudioFormatReader (nullptr, "ARAPlaybackRegionReader") +{ + // We're only providing the minimal set of meaningful values, since the ARA renderer should only + // look at the time position and the playing state, and read any related tempo or bar signature + // information from the ARA model directly (MusicalContext). + positionInfo.resetToDefault(); + positionInfo.isPlaying = true; + + sampleRate = rate; + numChannels = (unsigned int) numChans; + bitsPerSample = 32; + usesFloatingPointData = true; + + auto* documentController = (! playbackRegions.empty()) + ? playbackRegions.front()->getDocumentController() + : nullptr; + + playbackRenderer.reset (documentController ? static_cast (documentController->doCreatePlaybackRenderer()) + : nullptr); + + if (playbackRenderer != nullptr) + { + double regionsStartTime = std::numeric_limits::max(); + double regionsEndTime = std::numeric_limits::lowest(); + + for (const auto& playbackRegion : playbackRegions) + { + jassert (playbackRegion->getDocumentController() == documentController); + auto playbackRegionTimeRange = playbackRegion->getTimeRange (ARAPlaybackRegion::IncludeHeadAndTail::yes); + regionsStartTime = jmin (regionsStartTime, playbackRegionTimeRange.getStart()); + regionsEndTime = jmax (regionsEndTime, playbackRegionTimeRange.getEnd()); + + playbackRenderer->addPlaybackRegion (ARA::PlugIn::toRef (playbackRegion)); + playbackRegion->addListener (this); + } + + startInSamples = (int64) (regionsStartTime * sampleRate + 0.5); + lengthInSamples = (int64) ((regionsEndTime - regionsStartTime) * sampleRate + 0.5); + + playbackRenderer->prepareToPlay (rate, + maximumBlockSize, + numChans, + AudioProcessor::ProcessingPrecision::singlePrecision, + ARARenderer::AlwaysNonRealtime::yes); + } + else + { + startInSamples = 0; + lengthInSamples = 0; + } +} + +ARAPlaybackRegionReader::~ARAPlaybackRegionReader() +{ + invalidate(); +} + +void ARAPlaybackRegionReader::invalidate() +{ + ScopedWriteLock scopedWrite (lock); + + if (! isValid()) + return; + + for (auto& playbackRegion : playbackRenderer->getPlaybackRegions()) + playbackRegion->removeListener (this); + + playbackRenderer->releaseResources(); + playbackRenderer.reset(); +} + +bool ARAPlaybackRegionReader::readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer, + int64 startSampleInFile, int numSamples) +{ + bool success = false; + bool needClearSamples = true; + + const ScopedTryReadLock readLock (lock); + + if (readLock.isLocked()) + { + if (isValid()) + { + success = true; + needClearSamples = false; + positionInfo.timeInSamples = startSampleInFile + startInSamples; + while (numSamples > 0) + { + const int numSliceSamples = jmin (numSamples, maximumBlockSize); + AudioBuffer buffer ((float **) destSamples, numDestChannels, startOffsetInDestBuffer, numSliceSamples); + positionInfo.timeInSeconds = static_cast (positionInfo.timeInSamples) / sampleRate; + success &= playbackRenderer->processBlock (buffer, AudioProcessor::Realtime::no, positionInfo); + numSamples -= numSliceSamples; + startOffsetInDestBuffer += numSliceSamples; + positionInfo.timeInSamples += numSliceSamples; + } + } + } + + if (needClearSamples) + for (int chan_i = 0; chan_i < numDestChannels; ++chan_i) + FloatVectorOperations::clear ((float *) destSamples[chan_i], numSamples); + + return success; +} + +void ARAPlaybackRegionReader::willUpdatePlaybackRegionProperties (ARAPlaybackRegion* playbackRegion, ARAPlaybackRegion::PropertiesPtr newProperties) +{ + jassert (ARA::contains (playbackRenderer->getPlaybackRegions(), playbackRegion)); + + if ((playbackRegion->getStartInAudioModificationTime() != newProperties->startInModificationTime) + || (playbackRegion->getDurationInAudioModificationTime() != newProperties->durationInModificationTime) + || (playbackRegion->getStartInPlaybackTime() != newProperties->startInPlaybackTime) + || (playbackRegion->getDurationInPlaybackTime() != newProperties->durationInPlaybackTime) + || (playbackRegion->isTimestretchEnabled() != ((newProperties->transformationFlags & ARA::kARAPlaybackTransformationTimestretch) != 0)) + || (playbackRegion->isTimeStretchReflectingTempo() != ((newProperties->transformationFlags & ARA::kARAPlaybackTransformationTimestretchReflectingTempo) != 0)) + || (playbackRegion->hasContentBasedFadeAtHead() != ((newProperties->transformationFlags & ARA::kARAPlaybackTransformationContentBasedFadeAtHead) != 0)) + || (playbackRegion->hasContentBasedFadeAtTail() != ((newProperties->transformationFlags & ARA::kARAPlaybackTransformationContentBasedFadeAtTail) != 0))) + { + invalidate(); + } +} + +void ARAPlaybackRegionReader::didUpdatePlaybackRegionContent (ARAPlaybackRegion* playbackRegion, + ARAContentUpdateScopes scopeFlags) +{ + jassertquiet (ARA::contains (playbackRenderer->getPlaybackRegions(), playbackRegion)); + + // Invalidate if the audio signal is changed + if (scopeFlags.affectSamples()) + invalidate(); +} + +void ARAPlaybackRegionReader::willDestroyPlaybackRegion (ARAPlaybackRegion* playbackRegion) +{ + jassertquiet (ARA::contains (playbackRenderer->getPlaybackRegions(), playbackRegion)); + + invalidate(); +} + +} // namespace juce diff --git a/modules/juce_audio_formats/format/juce_ARAAudioReaders.h b/modules/juce_audio_formats/format/juce_ARAAudioReaders.h new file mode 100644 index 0000000000..00c51f1a5a --- /dev/null +++ b/modules/juce_audio_formats/format/juce_ARAAudioReaders.h @@ -0,0 +1,187 @@ +/* + ============================================================================== + + This file is part of the JUCE 7 technical preview. + Copyright (c) 2022 - Raw Material Software Limited + + You may use this code under the terms of the GPL v3 + (see www.gnu.org/licenses). + + For the technical preview this file cannot be licensed commercially. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#pragma once + +namespace juce +{ + +class AudioProcessor; + +/* All these readers follow a common pattern of "invalidation": + + Whenever the samples they are reading are altered, the readers become invalid and will stop + accessing the model graph. These alterations are model edits such as property changes, content + changes (if affecting sample scope), or the deletion of some model object involved in the read + process. Since these edits are performed on the document controller thread, reader validity can + immediately be checked after the edit has been concluded, and any reader that has become invalid + can be recreated. + + Note that encountering a failure in any individual read call does not invalidate the reader, so + that the entity using the reader can decide whether to retry or to back out. This includes trying + to read an audio source for which the host has currently disabled access: the failure will be + immediately visible, but the reader will remain valid. This ensures that for example a realtime + renderer can just keep reading and will be seeing proper samples again once sample access is + re-enabled. + + If desired, the code calling readSamples() can also implement proper signaling of any read error + to the document controller thread to trigger rebuilding the reader as needed. This will typically + be done when implementing audio source analysis: if there is an error upon reading the samples + that cannot be resolved within a reasonable timeout, then the analysis would be aborted. The + document controller code that monitors the analysis tasks can evaluate this and re-launch a new + analysis when appropriate (e.g. when access is re-enabled). + + When reading playback regions (directly or through a region sequence reader), the reader will + represent the regions as a single source object that covers the union of all affected regions. + The first sample produced by the reader thus will be the first sample of the earliest region. + This means that the location of this region has to be taken into account by the calling code if + it wants to relate the samples to the model or any other reader output. +*/ + +//============================================================================== +/** + Subclass of AudioFormatReader that reads samples from a single ARA audio source. + + Plug-Ins typically use this from their rendering code, wrapped in a BufferingAudioReader + to bridge between realtime rendering and non-realtime audio reading. + + The reader becomes invalidated if + - the audio source content is updated in a way that affects its samples, + - the audio source sample access is disabled, or + - the audio source being read is destroyed. + + @tags{ARA} +*/ +class JUCE_API ARAAudioSourceReader : public AudioFormatReader, + private ARAAudioSource::Listener +{ +public: + /** Use an ARAAudioSource to construct an audio source reader for the given \p audioSource. */ + explicit ARAAudioSourceReader (ARAAudioSource* audioSource); + + ~ARAAudioSourceReader() override; + + bool readSamples (int** destSamples, + int numDestChannels, + int startOffsetInDestBuffer, + int64 startSampleInFile, + int numSamples) override; + + /** Returns true as long as the reader's underlying ARAAudioSource remains accessible and its + sample content is not changed. + */ + bool isValid() const { return audioSourceBeingRead != nullptr; } + + /** Invalidate the reader - the reader will call this internally if needed, but can also be + invalidated from the outside (from message thread only!). + */ + void invalidate(); + + void willUpdateAudioSourceProperties (ARAAudioSource* audioSource, + ARAAudioSource::PropertiesPtr newProperties) override; + void doUpdateAudioSourceContent (ARAAudioSource* audioSource, + ARAContentUpdateScopes scopeFlags) override; + void willEnableAudioSourceSamplesAccess (ARAAudioSource* audioSource, bool enable) override; + void didEnableAudioSourceSamplesAccess (ARAAudioSource* audioSource, bool enable) override; + void willDestroyAudioSource (ARAAudioSource* audioSource) override; + +private: + ARAAudioSource* audioSourceBeingRead; + std::unique_ptr hostReader; + ReadWriteLock lock; + std::vector tmpPtrs; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARAAudioSourceReader) +}; + +//============================================================================== +/** + Subclass of AudioFormatReader that reads samples from a group of playback regions. + + Plug-Ins typically use this to draw the output of a playback region in their UI. + + In order to read from playback regions, the reader requires an audio processor that acts as ARA + playback renderer. Configuring the audio processor for real-time operation results in the reader + being real-time capable too, unlike most other AudioFormatReaders. The reader instance will take + care of adding all regions being read to the renderer and invoke its processBlock function in + order to read the region samples. + + The reader becomes invalid if + - any region properties are updated in a way that would affect its samples, + - any region content is updated in a way that would affect its samples, or + - any of its regions are destroyed. + + @tags{ARA} +*/ +class JUCE_API ARAPlaybackRegionReader : public AudioFormatReader, + private ARAPlaybackRegion::Listener +{ +public: + /** Create an ARAPlaybackRegionReader instance to read the given \p playbackRegion, using the + sample rate and channel count of the underlying ARAAudioSource. + + @param playbackRegion The playback region that should be read - must not be nullptr! + */ + explicit ARAPlaybackRegionReader (ARAPlaybackRegion* playbackRegion); + + /** Create an ARAPlaybackRegionReader instance to read the given \p playbackRegions + + @param sampleRate The sample rate that should be used for reading. + @param numChannels The channel count that should be used for reading. + @param playbackRegions The vector of playback regions that should be read - must not be empty! + All regions must be part of the same ARADocument. + */ + ARAPlaybackRegionReader (double sampleRate, int numChannels, + const std::vector& playbackRegions); + + ~ARAPlaybackRegionReader() override; + + /** Returns true as long as any of the reader's underlying playback region's haven't changed. */ + bool isValid() const { return (playbackRenderer != nullptr); } + + /** Invalidate the reader - this should be called if the sample content of any of the reader's + ARAPlaybackRegions changes. + */ + void invalidate(); + + bool readSamples (int** destSamples, + int numDestChannels, + int startOffsetInDestBuffer, + int64 startSampleInFile, + int numSamples) override; + + void willUpdatePlaybackRegionProperties (ARAPlaybackRegion* playbackRegion, + ARAPlaybackRegion::PropertiesPtr newProperties) override; + void didUpdatePlaybackRegionContent (ARAPlaybackRegion* playbackRegion, + ARAContentUpdateScopes scopeFlags) override; + void willDestroyPlaybackRegion (ARAPlaybackRegion* playbackRegion) override; + + /** The starting point of the reader in playback samples */ + int64 startInSamples = 0; + +private: + std::unique_ptr playbackRenderer; + AudioPlayHead::CurrentPositionInfo positionInfo; + ReadWriteLock lock; + + static constexpr int maximumBlockSize = 4 * 1024; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARAPlaybackRegionReader) +}; + +} // namespace juce diff --git a/modules/juce_audio_formats/juce_audio_formats.cpp b/modules/juce_audio_formats/juce_audio_formats.cpp index 1b1e600c44..eb02bbc107 100644 --- a/modules/juce_audio_formats/juce_audio_formats.cpp +++ b/modules/juce_audio_formats/juce_audio_formats.cpp @@ -61,6 +61,11 @@ #include "codecs/juce_WavAudioFormat.cpp" #include "codecs/juce_LAMEEncoderAudioFormat.cpp" +#if JucePlugin_Enable_ARA + #include "juce_audio_processors/utilities/ARA/juce_ARADocumentControllerCommon.cpp" + #include "format/juce_ARAAudioReaders.cpp" +#endif + #if JUCE_WINDOWS && JUCE_USE_WINDOWS_MEDIA_FORMAT #include "codecs/juce_WindowsMediaAudioFormat.cpp" #endif diff --git a/modules/juce_audio_formats/juce_audio_formats.h b/modules/juce_audio_formats/juce_audio_formats.h index 2e0c81671e..a94c42eb8c 100644 --- a/modules/juce_audio_formats/juce_audio_formats.h +++ b/modules/juce_audio_formats/juce_audio_formats.h @@ -121,3 +121,9 @@ #include "codecs/juce_WavAudioFormat.h" #include "codecs/juce_WindowsMediaAudioFormat.h" #include "sampler/juce_Sampler.h" + +#if JucePlugin_Enable_ARA + #include + + #include "format/juce_ARAAudioReaders.h" +#endif diff --git a/modules/juce_audio_processors/juce_audio_processors.cpp b/modules/juce_audio_processors/juce_audio_processors.cpp index ce2036783e..526f32b4d7 100644 --- a/modules/juce_audio_processors/juce_audio_processors.cpp +++ b/modules/juce_audio_processors/juce_audio_processors.cpp @@ -219,6 +219,7 @@ private: #include "utilities/juce_AudioProcessorValueTreeState.cpp" #include "utilities/juce_PluginHostType.cpp" #include "utilities/juce_NativeScaleFactorNotifier.cpp" +#include "utilities/ARA/juce_ARA_utils.cpp" #include "format_types/juce_LV2PluginFormat.cpp" diff --git a/modules/juce_audio_processors/juce_audio_processors.h b/modules/juce_audio_processors/juce_audio_processors.h index 99c107ae8d..9d5b62a255 100644 --- a/modules/juce_audio_processors/juce_audio_processors.h +++ b/modules/juce_audio_processors/juce_audio_processors.h @@ -160,6 +160,7 @@ #include "utilities/juce_ParameterAttachments.h" #include "utilities/juce_AudioProcessorValueTreeState.h" #include "utilities/juce_PluginHostType.h" +#include "utilities/ARA/juce_ARA_utils.h" // This is here to avoid missing-prototype warnings in user code. // If you're implementing a plugin, you should supply a body for diff --git a/modules/juce_audio_processors/juce_audio_processors_ara.cpp b/modules/juce_audio_processors/juce_audio_processors_ara.cpp index 8f16f9beeb..77df012e4d 100644 --- a/modules/juce_audio_processors/juce_audio_processors_ara.cpp +++ b/modules/juce_audio_processors/juce_audio_processors_ara.cpp @@ -24,7 +24,7 @@ To prevent such problems it's easiest to have it in its own translation unit. */ -#if (JUCE_PLUGINHOST_ARA && (JUCE_PLUGINHOST_VST3 || JUCE_PLUGINHOST_AU) && (JUCE_MAC || JUCE_WINDOWS)) +#if (JucePlugin_Enable_ARA || (JUCE_PLUGINHOST_ARA && (JUCE_PLUGINHOST_VST3 || JUCE_PLUGINHOST_AU))) && (JUCE_MAC || JUCE_WINDOWS) JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wgnu-zero-variadic-macro-arguments", "-Wmissing-prototypes") #include diff --git a/modules/juce_audio_processors/utilities/ARA/juce_ARADocumentController.cpp b/modules/juce_audio_processors/utilities/ARA/juce_ARADocumentController.cpp new file mode 100644 index 0000000000..cbac951f0e --- /dev/null +++ b/modules/juce_audio_processors/utilities/ARA/juce_ARADocumentController.cpp @@ -0,0 +1,964 @@ +/* + ============================================================================== + + This file is part of the JUCE 7 technical preview. + Copyright (c) 2022 - Raw Material Software Limited + + You may use this code under the terms of the GPL v3 + (see www.gnu.org/licenses). + + For the technical preview this file cannot be licensed commercially. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +class ARADocumentControllerSpecialisation::ARADocumentControllerImpl : public ARADocumentController, + private juce::Timer +{ +public: + ARADocumentControllerImpl (const ARA::PlugIn::PlugInEntry* entry, + const ARA::ARADocumentControllerHostInstance* instance, + ARADocumentControllerSpecialisation* spec) + : ARADocumentController (entry, instance), specialisation (spec) + { + } + + template + std::vector const& getPlaybackRenderers() const noexcept + { + return ARA::PlugIn::DocumentController::getPlaybackRenderers(); + } + + template + std::vector const& getEditorRenderers() const noexcept + { + return ARA::PlugIn::DocumentController::getEditorRenderers(); + } + + template + std::vector const& getEditorViews() const noexcept + { + return ARA::PlugIn::DocumentController::getEditorViews(); + } + + auto getSpecialisation() { return specialisation; } + +protected: + //============================================================================== + bool doRestoreObjectsFromStream (ARAInputStream& input, const ARARestoreObjectsFilter* filter) noexcept + { + return specialisation->doRestoreObjectsFromStream (input, filter); + } + + bool doStoreObjectsToStream (ARAOutputStream& output, const ARAStoreObjectsFilter* filter) noexcept + { + return specialisation->doStoreObjectsToStream (output, filter); + } + + //============================================================================== + // Model object creation + ARA::PlugIn::Document* doCreateDocument () noexcept override; + ARA::PlugIn::MusicalContext* doCreateMusicalContext (ARA::PlugIn::Document* document, ARA::ARAMusicalContextHostRef hostRef) noexcept override; + ARA::PlugIn::RegionSequence* doCreateRegionSequence (ARA::PlugIn::Document* document, ARA::ARARegionSequenceHostRef hostRef) noexcept override; + ARA::PlugIn::AudioSource* doCreateAudioSource (ARA::PlugIn::Document* document, ARA::ARAAudioSourceHostRef hostRef) noexcept override; + ARA::PlugIn::AudioModification* doCreateAudioModification (ARA::PlugIn::AudioSource* audioSource, ARA::ARAAudioModificationHostRef hostRef, const ARA::PlugIn::AudioModification* optionalModificationToClone) noexcept override; + ARA::PlugIn::PlaybackRegion* doCreatePlaybackRegion (ARA::PlugIn::AudioModification* modification, ARA::ARAPlaybackRegionHostRef hostRef) noexcept override; + + //============================================================================== + // Plugin role implementation + friend class ARAPlaybackRegionReader; + ARA::PlugIn::PlaybackRenderer* doCreatePlaybackRenderer() noexcept override; + ARA::PlugIn::EditorRenderer* doCreateEditorRenderer() noexcept override; + ARA::PlugIn::EditorView* doCreateEditorView() noexcept override; + + //============================================================================== + // ARAAudioSource content access + bool doIsAudioSourceContentAvailable (const ARA::PlugIn::AudioSource* audioSource, + ARA::ARAContentType type) noexcept override; + ARA::ARAContentGrade doGetAudioSourceContentGrade (const ARA::PlugIn::AudioSource* audioSource, + ARA::ARAContentType type) noexcept override; + ARA::PlugIn::ContentReader* doCreateAudioSourceContentReader (ARA::PlugIn::AudioSource* audioSource, + ARA::ARAContentType type, + const ARA::ARAContentTimeRange* range) noexcept override; + + //============================================================================== + // ARAAudioModification content access + bool doIsAudioModificationContentAvailable (const ARA::PlugIn::AudioModification* audioModification, + ARA::ARAContentType type) noexcept override; + ARA::ARAContentGrade doGetAudioModificationContentGrade (const ARA::PlugIn::AudioModification* audioModification, + ARA::ARAContentType type) noexcept override; + ARA::PlugIn::ContentReader* doCreateAudioModificationContentReader (ARA::PlugIn::AudioModification* audioModification, + ARA::ARAContentType type, + const ARA::ARAContentTimeRange* range) noexcept override; + + //============================================================================== + // ARAPlaybackRegion content access + bool doIsPlaybackRegionContentAvailable (const ARA::PlugIn::PlaybackRegion* playbackRegion, + ARA::ARAContentType type) noexcept override; + ARA::ARAContentGrade doGetPlaybackRegionContentGrade (const ARA::PlugIn::PlaybackRegion* playbackRegion, + ARA::ARAContentType type) noexcept override; + ARA::PlugIn::ContentReader* doCreatePlaybackRegionContentReader (ARA::PlugIn::PlaybackRegion* playbackRegion, + ARA::ARAContentType type, + const ARA::ARAContentTimeRange* range) noexcept override; + + //============================================================================== + // ARAAudioSource analysis + bool doIsAudioSourceContentAnalysisIncomplete (const ARA::PlugIn::AudioSource* audioSource, + ARA::ARAContentType type) noexcept override; + void doRequestAudioSourceContentAnalysis (ARA::PlugIn::AudioSource* audioSource, + std::vector const& contentTypes) noexcept override; + + //============================================================================== + // Analysis Algorithm selection + ARA::ARAInt32 doGetProcessingAlgorithmsCount() noexcept override; + const ARA::ARAProcessingAlgorithmProperties* doGetProcessingAlgorithmProperties (ARA::ARAInt32 algorithmIndex) noexcept override; + ARA::ARAInt32 doGetProcessingAlgorithmForAudioSource (const ARA::PlugIn::AudioSource* audioSource) noexcept override; + void doRequestProcessingAlgorithmForAudioSource (ARA::PlugIn::AudioSource* audioSource, + ARA::ARAInt32 algorithmIndex) noexcept override; + +#ifndef DOXYGEN + + //============================================================================== + bool doRestoreObjectsFromArchive (ARA::PlugIn::HostArchiveReader* archiveReader, const ARA::PlugIn::RestoreObjectsFilter* filter) noexcept override; + bool doStoreObjectsToArchive (ARA::PlugIn::HostArchiveWriter* archiveWriter, const ARA::PlugIn::StoreObjectsFilter* filter) noexcept override; + + //============================================================================== + // Document notifications + void willBeginEditing() noexcept override; + void didEndEditing() noexcept override; + void willNotifyModelUpdates() noexcept override; + void didNotifyModelUpdates() noexcept override; + void willUpdateDocumentProperties (ARA::PlugIn::Document* document, ARADocument::PropertiesPtr newProperties) noexcept override; + void didUpdateDocumentProperties (ARA::PlugIn::Document* document) noexcept override; + void didAddMusicalContextToDocument (ARA::PlugIn::Document* document, ARA::PlugIn::MusicalContext* musicalContext) noexcept override; + void willRemoveMusicalContextFromDocument (ARA::PlugIn::Document* document, ARA::PlugIn::MusicalContext* musicalContext) noexcept override; + void didReorderMusicalContextsInDocument (ARA::PlugIn::Document* document) noexcept override; + void didAddRegionSequenceToDocument (ARA::PlugIn::Document* document, ARA::PlugIn::RegionSequence* regionSequence) noexcept override; + void willRemoveRegionSequenceFromDocument (ARA::PlugIn::Document* document, ARA::PlugIn::RegionSequence* regionSequence) noexcept override; + void didReorderRegionSequencesInDocument (ARA::PlugIn::Document* document) noexcept override; + void didAddAudioSourceToDocument (ARA::PlugIn::Document* document, ARA::PlugIn::AudioSource* audioSource) noexcept override; + void willRemoveAudioSourceFromDocument (ARA::PlugIn::Document* document, ARA::PlugIn::AudioSource* audioSource) noexcept override; + void willDestroyDocument (ARA::PlugIn::Document* document) noexcept override; + + //============================================================================== + // MusicalContext notifications + void willUpdateMusicalContextProperties (ARA::PlugIn::MusicalContext* musicalContext, ARAMusicalContext::PropertiesPtr newProperties) noexcept override; + void didUpdateMusicalContextProperties (ARA::PlugIn::MusicalContext* musicalContext) noexcept override; + void doUpdateMusicalContextContent (ARA::PlugIn::MusicalContext* musicalContext, const ARA::ARAContentTimeRange* range, ARA::ContentUpdateScopes flags) noexcept override; + void didAddRegionSequenceToMusicalContext (ARA::PlugIn::MusicalContext* musicalContext, ARA::PlugIn::RegionSequence* regionSequence) noexcept override; + void willRemoveRegionSequenceFromMusicalContext (ARA::PlugIn::MusicalContext* musicalContext, ARA::PlugIn::RegionSequence* regionSequence) noexcept override; + void didReorderRegionSequencesInMusicalContext (ARA::PlugIn::MusicalContext* musicalContext) noexcept override; + void willDestroyMusicalContext (ARA::PlugIn::MusicalContext* musicalContext) noexcept override; + + //============================================================================== + // RegionSequence notifications, typically not overridden further + void willUpdateRegionSequenceProperties (ARA::PlugIn::RegionSequence* regionSequence, ARARegionSequence::PropertiesPtr newProperties) noexcept override; + void didUpdateRegionSequenceProperties (ARA::PlugIn::RegionSequence* regionSequence) noexcept override; + void didAddPlaybackRegionToRegionSequence (ARA::PlugIn::RegionSequence* regionSequence, ARA::PlugIn::PlaybackRegion* playbackRegion) noexcept override; + void willRemovePlaybackRegionFromRegionSequence (ARA::PlugIn::RegionSequence* regionSequence, ARA::PlugIn::PlaybackRegion* playbackRegion) noexcept override; + void willDestroyRegionSequence (ARA::PlugIn::RegionSequence* regionSequence) noexcept override; + + //============================================================================== + // AudioSource notifications + void willUpdateAudioSourceProperties (ARA::PlugIn::AudioSource* audioSource, ARAAudioSource::PropertiesPtr newProperties) noexcept override; + void didUpdateAudioSourceProperties (ARA::PlugIn::AudioSource* audioSource) noexcept override; + void doUpdateAudioSourceContent (ARA::PlugIn::AudioSource* audioSource, const ARA::ARAContentTimeRange* range, ARA::ContentUpdateScopes flags) noexcept override; + void willEnableAudioSourceSamplesAccess (ARA::PlugIn::AudioSource* audioSource, bool enable) noexcept override; + void didEnableAudioSourceSamplesAccess (ARA::PlugIn::AudioSource* audioSource, bool enable) noexcept override; + void didAddAudioModificationToAudioSource (ARA::PlugIn::AudioSource* audioSource, ARA::PlugIn::AudioModification* audioModification) noexcept override; + void willRemoveAudioModificationFromAudioSource (ARA::PlugIn::AudioSource* audioSource, ARA::PlugIn::AudioModification* audioModification) noexcept override; + void willDeactivateAudioSourceForUndoHistory (ARA::PlugIn::AudioSource* audioSource, bool deactivate) noexcept override; + void didDeactivateAudioSourceForUndoHistory (ARA::PlugIn::AudioSource* audioSource, bool deactivate) noexcept override; + void willDestroyAudioSource (ARA::PlugIn::AudioSource* audioSource) noexcept override; + + //============================================================================== + // AudioModification notifications + void willUpdateAudioModificationProperties (ARA::PlugIn::AudioModification* audioModification, ARAAudioModification::PropertiesPtr newProperties) noexcept override; + void didUpdateAudioModificationProperties (ARA::PlugIn::AudioModification* audioModification) noexcept override; + void didAddPlaybackRegionToAudioModification (ARA::PlugIn::AudioModification* audioModification, ARA::PlugIn::PlaybackRegion* playbackRegion) noexcept override; + void willRemovePlaybackRegionFromAudioModification (ARA::PlugIn::AudioModification* audioModification, ARA::PlugIn::PlaybackRegion* playbackRegion) noexcept override; + void willDeactivateAudioModificationForUndoHistory (ARA::PlugIn::AudioModification* audioModification, bool deactivate) noexcept override; + void didDeactivateAudioModificationForUndoHistory (ARA::PlugIn::AudioModification* audioModification, bool deactivate) noexcept override; + void willDestroyAudioModification (ARA::PlugIn::AudioModification* audioModification) noexcept override; + + //============================================================================== + // PlaybackRegion notifications + void willUpdatePlaybackRegionProperties (ARA::PlugIn::PlaybackRegion* playbackRegion, ARAPlaybackRegion::PropertiesPtr newProperties) noexcept override; + void didUpdatePlaybackRegionProperties (ARA::PlugIn::PlaybackRegion* playbackRegion) noexcept override; + void willDestroyPlaybackRegion (ARA::PlugIn::PlaybackRegion* playbackRegion) noexcept override; + + //============================================================================== + // juce::Timer overrides + void timerCallback() override; + +public: + //============================================================================== + /** @internal */ + void internalNotifyAudioSourceAnalysisProgressStarted (ARAAudioSource* audioSource) override; + + /** @internal */ + void internalNotifyAudioSourceAnalysisProgressUpdated (ARAAudioSource* audioSource, float progress) override; + + /** @internal */ + void internalNotifyAudioSourceAnalysisProgressCompleted (ARAAudioSource* audioSource) override; + + /** @internal */ + void internalDidUpdateAudioSourceAnalysisProgress (ARAAudioSource* audioSource, + ARAAudioSource::ARAAnalysisProgressState state, + float progress) override; + + //============================================================================== + /** @internal */ + void internalNotifyAudioSourceContentChanged (ARAAudioSource* audioSource, + ARAContentUpdateScopes scopeFlags, + bool notifyARAHost) override; + + /** @internal */ + void internalNotifyAudioModificationContentChanged (ARAAudioModification* audioModification, + ARAContentUpdateScopes scopeFlags, + bool notifyARAHost) override; + + /** @internal */ + void internalNotifyPlaybackRegionContentChanged (ARAPlaybackRegion* playbackRegion, + ARAContentUpdateScopes scopeFlags, + bool notifyARAHost) override; + +#endif + +private: + //============================================================================== + ARADocumentControllerSpecialisation* specialisation; + std::atomic internalAnalysisProgressIsSynced { true }; + ScopedJuceInitialiser_GUI libraryInitialiser; + int activeAudioSourcesCount = 0; + + //============================================================================== + template + void notifyListeners (Function ModelObject::Listener::* function, ModelObject* modelObject, Ts... ts) + { + (specialisation->*function) (modelObject, ts...); + modelObject->notifyListeners ([&] (auto& l) + { + try + { + (l.*function) (modelObject, ts...); + } + catch (...) + { + } + }); + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARADocumentControllerImpl) +}; + +ARA::PlugIn::DocumentController* ARADocumentControllerSpecialisation::getDocumentController() noexcept +{ + return documentController.get(); +} + +//============================================================================== +void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::internalNotifyAudioSourceAnalysisProgressStarted (ARAAudioSource* audioSource) +{ + if (audioSource->internalAnalysisProgressTracker.updateProgress (ARA::kARAAnalysisProgressStarted, 0.0f)) + internalAnalysisProgressIsSynced.store (false, std::memory_order_release); + + DocumentController::notifyAudioSourceAnalysisProgressStarted (audioSource); +} + +void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::internalNotifyAudioSourceAnalysisProgressUpdated (ARAAudioSource* audioSource, + float progress) +{ + if (audioSource->internalAnalysisProgressTracker.updateProgress (ARA::kARAAnalysisProgressUpdated, progress)) + internalAnalysisProgressIsSynced.store (false, std::memory_order_release); + + DocumentController::notifyAudioSourceAnalysisProgressUpdated (audioSource, progress); +} + +void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::internalNotifyAudioSourceAnalysisProgressCompleted (ARAAudioSource* audioSource) +{ + if (audioSource->internalAnalysisProgressTracker.updateProgress (ARA::kARAAnalysisProgressCompleted, 1.0f)) + internalAnalysisProgressIsSynced.store (false, std::memory_order_release); + + DocumentController::notifyAudioSourceAnalysisProgressCompleted (audioSource); +} + +void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::internalDidUpdateAudioSourceAnalysisProgress (ARAAudioSource* audioSource, + ARAAudioSource::ARAAnalysisProgressState state, + float progress) +{ + specialisation->didUpdateAudioSourceAnalysisProgress (audioSource, state, progress); +} + +//============================================================================== +ARADocumentControllerSpecialisation* ARADocumentControllerSpecialisation::getSpecialisedDocumentControllerImpl (ARA::PlugIn::DocumentController* dc) +{ + return static_cast (dc)->getSpecialisation(); +} + +ARADocument* ARADocumentControllerSpecialisation::getDocumentImpl() +{ + return documentController->getDocument(); +} + +//============================================================================== +// some helper macros to ease repeated declaration & implementation of notification functions below: +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wgnu-zero-variadic-macro-arguments") + +// no notification arguments +#define OVERRIDE_TO_NOTIFY_1(function, ModelObjectType, modelObject) \ + void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::function (ARA::PlugIn::ModelObjectType* modelObject) noexcept \ + { \ + notifyListeners (&ARA##ModelObjectType::Listener::function, static_cast (modelObject)); \ + } + +// single notification argument, model object version +#define OVERRIDE_TO_NOTIFY_2(function, ModelObjectType, modelObject, ArgumentType, argument) \ + void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::function (ARA::PlugIn::ModelObjectType* modelObject, ARA::PlugIn::ArgumentType argument) noexcept \ + { \ + notifyListeners (&ARA##ModelObjectType::Listener::function, static_cast (modelObject), static_cast (argument)); \ + } + +// single notification argument, non-model object version +#define OVERRIDE_TO_NOTIFY_3(function, ModelObjectType, modelObject, ArgumentType, argument) \ + void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::function (ARA::PlugIn::ModelObjectType* modelObject, ArgumentType argument) noexcept \ + { \ + notifyListeners (&ARA##ModelObjectType::Listener::function, static_cast (modelObject), argument); \ + } + +//============================================================================== +ARA::PlugIn::Document* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doCreateDocument() noexcept +{ + auto* document = specialisation->doCreateDocument(); + + // Your Document subclass must inherit from juce::ARADocument + jassert (dynamic_cast (document)); + + return document; +} + +void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::willBeginEditing() noexcept +{ + notifyListeners (&ARADocument::Listener::willBeginEditing, static_cast (getDocument())); +} + +void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::didEndEditing() noexcept +{ + notifyListeners (&ARADocument::Listener::didEndEditing, static_cast (getDocument())); + + if (isTimerRunning() && (activeAudioSourcesCount == 0)) + stopTimer(); + else if (! isTimerRunning() && (activeAudioSourcesCount > 0)) + startTimerHz (20); +} + +void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::willNotifyModelUpdates() noexcept +{ + notifyListeners (&ARADocument::Listener::willNotifyModelUpdates, static_cast (getDocument())); +} + +void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::didNotifyModelUpdates() noexcept +{ + notifyListeners (&ARADocument::Listener::didNotifyModelUpdates, static_cast (getDocument())); +} + +//============================================================================== +bool ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doRestoreObjectsFromArchive (ARA::PlugIn::HostArchiveReader* archiveReader, + const ARA::PlugIn::RestoreObjectsFilter* filter) noexcept +{ + ARAInputStream reader (archiveReader); + return doRestoreObjectsFromStream (reader, filter); +} + +bool ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doStoreObjectsToArchive (ARA::PlugIn::HostArchiveWriter* archiveWriter, + const ARA::PlugIn::StoreObjectsFilter* filter) noexcept +{ + ARAOutputStream writer (archiveWriter); + return doStoreObjectsToStream (writer, filter); +} + +//============================================================================== +OVERRIDE_TO_NOTIFY_3 (willUpdateDocumentProperties, Document, document, ARADocument::PropertiesPtr, newProperties) +OVERRIDE_TO_NOTIFY_1 (didUpdateDocumentProperties, Document, document) +OVERRIDE_TO_NOTIFY_2 (didAddMusicalContextToDocument, Document, document, MusicalContext*, musicalContext) +OVERRIDE_TO_NOTIFY_2 (willRemoveMusicalContextFromDocument, Document, document, MusicalContext*, musicalContext) +OVERRIDE_TO_NOTIFY_1 (didReorderMusicalContextsInDocument, Document, document) +OVERRIDE_TO_NOTIFY_2 (didAddRegionSequenceToDocument, Document, document, RegionSequence*, regionSequence) +OVERRIDE_TO_NOTIFY_2 (willRemoveRegionSequenceFromDocument, Document, document, RegionSequence*, regionSequence) +OVERRIDE_TO_NOTIFY_1 (didReorderRegionSequencesInDocument, Document, document) +OVERRIDE_TO_NOTIFY_2 (didAddAudioSourceToDocument, Document, document, AudioSource*, audioSource) +OVERRIDE_TO_NOTIFY_2 (willRemoveAudioSourceFromDocument, Document, document, AudioSource*, audioSource) +OVERRIDE_TO_NOTIFY_1 (willDestroyDocument, Document, document) + +//============================================================================== +ARA::PlugIn::MusicalContext* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doCreateMusicalContext (ARA::PlugIn::Document* document, + ARA::ARAMusicalContextHostRef hostRef) noexcept +{ + return specialisation->doCreateMusicalContext (static_cast (document), hostRef); +} + +void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doUpdateMusicalContextContent (ARA::PlugIn::MusicalContext* musicalContext, + const ARA::ARAContentTimeRange*, + ARA::ContentUpdateScopes flags) noexcept +{ + notifyListeners (&ARAMusicalContext::Listener::doUpdateMusicalContextContent, + static_cast (musicalContext), + flags); +} + +OVERRIDE_TO_NOTIFY_3 (willUpdateMusicalContextProperties, MusicalContext, musicalContext, ARAMusicalContext::PropertiesPtr, newProperties) +OVERRIDE_TO_NOTIFY_1 (didUpdateMusicalContextProperties, MusicalContext, musicalContext) +OVERRIDE_TO_NOTIFY_2 (didAddRegionSequenceToMusicalContext, MusicalContext, musicalContext, RegionSequence*, regionSequence) +OVERRIDE_TO_NOTIFY_2 (willRemoveRegionSequenceFromMusicalContext, MusicalContext, musicalContext, RegionSequence*, regionSequence) +OVERRIDE_TO_NOTIFY_1 (didReorderRegionSequencesInMusicalContext, MusicalContext, musicalContext) +OVERRIDE_TO_NOTIFY_1 (willDestroyMusicalContext, MusicalContext, musicalContext) + +//============================================================================== +ARA::PlugIn::RegionSequence* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doCreateRegionSequence (ARA::PlugIn::Document* document, ARA::ARARegionSequenceHostRef hostRef) noexcept +{ + return specialisation->doCreateRegionSequence (static_cast (document), hostRef); +} + +OVERRIDE_TO_NOTIFY_3 (willUpdateRegionSequenceProperties, RegionSequence, regionSequence, ARARegionSequence::PropertiesPtr, newProperties) +OVERRIDE_TO_NOTIFY_1 (didUpdateRegionSequenceProperties, RegionSequence, regionSequence) +OVERRIDE_TO_NOTIFY_2 (didAddPlaybackRegionToRegionSequence, RegionSequence, regionSequence, PlaybackRegion*, playbackRegion) +OVERRIDE_TO_NOTIFY_2 (willRemovePlaybackRegionFromRegionSequence, RegionSequence, regionSequence, PlaybackRegion*, playbackRegion) +OVERRIDE_TO_NOTIFY_1 (willDestroyRegionSequence, RegionSequence, regionSequence) + +//============================================================================== +ARA::PlugIn::AudioSource* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doCreateAudioSource (ARA::PlugIn::Document* document, ARA::ARAAudioSourceHostRef hostRef) noexcept +{ + ++activeAudioSourcesCount; + return specialisation->doCreateAudioSource (static_cast (document), hostRef); +} + +void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doUpdateAudioSourceContent (ARA::PlugIn::AudioSource* audioSource, + const ARA::ARAContentTimeRange*, + ARA::ContentUpdateScopes flags) noexcept +{ + notifyListeners (&ARAAudioSource::Listener::doUpdateAudioSourceContent, static_cast (audioSource), flags); +} + +OVERRIDE_TO_NOTIFY_3 (willUpdateAudioSourceProperties, AudioSource, audioSource, ARAAudioSource::PropertiesPtr, newProperties) +OVERRIDE_TO_NOTIFY_1 (didUpdateAudioSourceProperties, AudioSource, audioSource) +OVERRIDE_TO_NOTIFY_3 (willEnableAudioSourceSamplesAccess, AudioSource, audioSource, bool, enable) +OVERRIDE_TO_NOTIFY_3 (didEnableAudioSourceSamplesAccess, AudioSource, audioSource, bool, enable) +OVERRIDE_TO_NOTIFY_2 (didAddAudioModificationToAudioSource, AudioSource, audioSource, AudioModification*, audioModification) +OVERRIDE_TO_NOTIFY_2 (willRemoveAudioModificationFromAudioSource, AudioSource, audioSource, AudioModification*, audioModification) +OVERRIDE_TO_NOTIFY_3 (willDeactivateAudioSourceForUndoHistory, AudioSource, audioSource, bool, deactivate) + +void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::didDeactivateAudioSourceForUndoHistory (ARA::PlugIn::AudioSource* audioSource, + bool deactivate) noexcept +{ + activeAudioSourcesCount += (deactivate ? -1 : 1); + notifyListeners (&ARAAudioSource::Listener::didDeactivateAudioSourceForUndoHistory, + static_cast (audioSource), + deactivate); +} + +void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::willDestroyAudioSource (ARA::PlugIn::AudioSource* audioSource) noexcept +{ + if (! audioSource->isDeactivatedForUndoHistory()) + --activeAudioSourcesCount; + + notifyListeners (&ARAAudioSource::Listener::willDestroyAudioSource, static_cast (audioSource)); +} +//============================================================================== +ARA::PlugIn::AudioModification* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doCreateAudioModification (ARA::PlugIn::AudioSource* audioSource, + ARA::ARAAudioModificationHostRef hostRef, + const ARA::PlugIn::AudioModification* optionalModificationToClone) noexcept +{ + return specialisation->doCreateAudioModification (static_cast (audioSource), + hostRef, + static_cast (optionalModificationToClone)); +} + +OVERRIDE_TO_NOTIFY_3 (willUpdateAudioModificationProperties, AudioModification, audioModification, ARAAudioModification::PropertiesPtr, newProperties) +OVERRIDE_TO_NOTIFY_1 (didUpdateAudioModificationProperties, AudioModification, audioModification) +OVERRIDE_TO_NOTIFY_2 (didAddPlaybackRegionToAudioModification, AudioModification, audioModification, PlaybackRegion*, playbackRegion) +OVERRIDE_TO_NOTIFY_2 (willRemovePlaybackRegionFromAudioModification, AudioModification, audioModification, PlaybackRegion*, playbackRegion) +OVERRIDE_TO_NOTIFY_3 (willDeactivateAudioModificationForUndoHistory, AudioModification, audioModification, bool, deactivate) +OVERRIDE_TO_NOTIFY_3 (didDeactivateAudioModificationForUndoHistory, AudioModification, audioModification, bool, deactivate) +OVERRIDE_TO_NOTIFY_1 (willDestroyAudioModification, AudioModification, audioModification) + +//============================================================================== +ARA::PlugIn::PlaybackRegion* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doCreatePlaybackRegion (ARA::PlugIn::AudioModification* modification, + ARA::ARAPlaybackRegionHostRef hostRef) noexcept +{ + return specialisation->doCreatePlaybackRegion (static_cast (modification), hostRef); +} + +OVERRIDE_TO_NOTIFY_3 (willUpdatePlaybackRegionProperties, PlaybackRegion, playbackRegion, ARAPlaybackRegion::PropertiesPtr, newProperties) +OVERRIDE_TO_NOTIFY_1 (didUpdatePlaybackRegionProperties, PlaybackRegion, playbackRegion) +OVERRIDE_TO_NOTIFY_1 (willDestroyPlaybackRegion, PlaybackRegion, playbackRegion) + +//============================================================================== +void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::internalNotifyAudioSourceContentChanged (ARAAudioSource* audioSource, + ARAContentUpdateScopes scopeFlags, + bool notifyARAHost) +{ + if (notifyARAHost) + DocumentController::notifyAudioSourceContentChanged (audioSource, scopeFlags); + + notifyListeners (&ARAAudioSource::Listener::doUpdateAudioSourceContent, audioSource, scopeFlags); +} + +void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::internalNotifyAudioModificationContentChanged (ARAAudioModification* audioModification, + ARAContentUpdateScopes scopeFlags, + bool notifyARAHost) +{ + if (notifyARAHost) + DocumentController::notifyAudioModificationContentChanged (audioModification, scopeFlags); + + notifyListeners (&ARAAudioModification::Listener::didUpdateAudioModificationContent, audioModification, scopeFlags); +} + +void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::internalNotifyPlaybackRegionContentChanged (ARAPlaybackRegion* playbackRegion, + ARAContentUpdateScopes scopeFlags, + bool notifyARAHost) +{ + if (notifyARAHost) + DocumentController::notifyPlaybackRegionContentChanged (playbackRegion, scopeFlags); + + notifyListeners (&ARAPlaybackRegion::Listener::didUpdatePlaybackRegionContent, playbackRegion, scopeFlags); +} + +//============================================================================== +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + +#undef OVERRIDE_TO_NOTIFY_1 +#undef OVERRIDE_TO_NOTIFY_2 +#undef OVERRIDE_TO_NOTIFY_3 + +//============================================================================== +ARADocument* ARADocumentControllerSpecialisation::doCreateDocument() +{ + return new ARADocument (static_cast (getDocumentController())); +} + +ARAMusicalContext* ARADocumentControllerSpecialisation::doCreateMusicalContext (ARADocument* document, + ARA::ARAMusicalContextHostRef hostRef) +{ + return new ARAMusicalContext (static_cast (document), hostRef); +} + +ARARegionSequence* ARADocumentControllerSpecialisation::doCreateRegionSequence (ARADocument* document, + ARA::ARARegionSequenceHostRef hostRef) +{ + return new ARARegionSequence (static_cast (document), hostRef); +} + +ARAAudioSource* ARADocumentControllerSpecialisation::doCreateAudioSource (ARADocument* document, + ARA::ARAAudioSourceHostRef hostRef) +{ + return new ARAAudioSource (static_cast (document), hostRef); +} + +ARAAudioModification* ARADocumentControllerSpecialisation::doCreateAudioModification ( + ARAAudioSource* audioSource, + ARA::ARAAudioModificationHostRef hostRef, + const ARAAudioModification* optionalModificationToClone) +{ + return new ARAAudioModification (static_cast (audioSource), + hostRef, + static_cast (optionalModificationToClone)); +} + +ARAPlaybackRegion* + ARADocumentControllerSpecialisation::doCreatePlaybackRegion (ARAAudioModification* modification, + ARA::ARAPlaybackRegionHostRef hostRef) +{ + return new ARAPlaybackRegion (static_cast (modification), hostRef); +} + +//============================================================================== +ARA::PlugIn::PlaybackRenderer* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doCreatePlaybackRenderer() noexcept +{ + return specialisation->doCreatePlaybackRenderer(); +} + +ARA::PlugIn::EditorRenderer* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doCreateEditorRenderer() noexcept +{ + return specialisation->doCreateEditorRenderer(); +} + +ARA::PlugIn::EditorView* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doCreateEditorView() noexcept +{ + return specialisation->doCreateEditorView(); +} + +bool ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doIsAudioSourceContentAvailable (const ARA::PlugIn::AudioSource* audioSource, + ARA::ARAContentType type) noexcept +{ + return specialisation->doIsAudioSourceContentAvailable (audioSource, type); +} + +ARA::ARAContentGrade ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doGetAudioSourceContentGrade (const ARA::PlugIn::AudioSource* audioSource, + ARA::ARAContentType type) noexcept +{ + return specialisation->doGetAudioSourceContentGrade (audioSource, type); +} + +ARA::PlugIn::ContentReader* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doCreateAudioSourceContentReader (ARA::PlugIn::AudioSource* audioSource, + ARA::ARAContentType type, + const ARA::ARAContentTimeRange* range) noexcept +{ + return specialisation->doCreateAudioSourceContentReader (audioSource, type, range); +} + +bool ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doIsAudioModificationContentAvailable (const ARA::PlugIn::AudioModification* audioModification, + ARA::ARAContentType type) noexcept +{ + return specialisation->doIsAudioModificationContentAvailable (audioModification, type); +} + +ARA::ARAContentGrade ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doGetAudioModificationContentGrade (const ARA::PlugIn::AudioModification* audioModification, + ARA::ARAContentType type) noexcept +{ + return specialisation->doGetAudioModificationContentGrade (audioModification, type); +} + +ARA::PlugIn::ContentReader* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doCreateAudioModificationContentReader (ARA::PlugIn::AudioModification* audioModification, + ARA::ARAContentType type, + const ARA::ARAContentTimeRange* range) noexcept +{ + return specialisation->doCreateAudioModificationContentReader (audioModification, type, range); +} + +bool ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doIsPlaybackRegionContentAvailable (const ARA::PlugIn::PlaybackRegion* playbackRegion, + ARA::ARAContentType type) noexcept +{ + return specialisation->doIsPlaybackRegionContentAvailable (playbackRegion, type); +} + +ARA::ARAContentGrade + ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doGetPlaybackRegionContentGrade (const ARA::PlugIn::PlaybackRegion* playbackRegion, + ARA::ARAContentType type) noexcept +{ + return specialisation->doGetPlaybackRegionContentGrade (playbackRegion, type); +} + +ARA::PlugIn::ContentReader* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doCreatePlaybackRegionContentReader (ARA::PlugIn::PlaybackRegion* playbackRegion, + ARA::ARAContentType type, + const ARA::ARAContentTimeRange* range) noexcept +{ + return specialisation->doCreatePlaybackRegionContentReader (playbackRegion, type, range); +} + +bool ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doIsAudioSourceContentAnalysisIncomplete (const ARA::PlugIn::AudioSource* audioSource, + ARA::ARAContentType type) noexcept +{ + return specialisation->doIsAudioSourceContentAnalysisIncomplete (audioSource, type); +} + +void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doRequestAudioSourceContentAnalysis (ARA::PlugIn::AudioSource* audioSource, + std::vector const& contentTypes) noexcept +{ + specialisation->doRequestAudioSourceContentAnalysis (audioSource, contentTypes); +} + +ARA::ARAInt32 ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doGetProcessingAlgorithmsCount() noexcept +{ + return specialisation->doGetProcessingAlgorithmsCount(); +} + +const ARA::ARAProcessingAlgorithmProperties* ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doGetProcessingAlgorithmProperties (ARA::ARAInt32 algorithmIndex) noexcept +{ + return specialisation->doGetProcessingAlgorithmProperties (algorithmIndex); +} + +ARA::ARAInt32 ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doGetProcessingAlgorithmForAudioSource (const ARA::PlugIn::AudioSource* audioSource) noexcept +{ + return specialisation->doGetProcessingAlgorithmForAudioSource (audioSource); +} + +void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doRequestProcessingAlgorithmForAudioSource (ARA::PlugIn::AudioSource* audioSource, + ARA::ARAInt32 algorithmIndex) noexcept +{ + return specialisation->doRequestProcessingAlgorithmForAudioSource (audioSource, algorithmIndex); +} + +//============================================================================== +// Helper code for ARADocumentControllerSpecialisation::ARADocumentControllerImpl::timerCallback() to +// rewire the host-related ARA SDK's progress tracker to our internal update mechanism. +namespace ModelUpdateControllerProgressAdapter +{ + using namespace ARA; + + static void ARA_CALL notifyAudioSourceAnalysisProgress (ARAModelUpdateControllerHostRef /*controllerHostRef*/, + ARAAudioSourceHostRef audioSourceHostRef, ARAAnalysisProgressState state, float value) noexcept + { + auto audioSource = reinterpret_cast (audioSourceHostRef); + audioSource->getDocumentController()->internalDidUpdateAudioSourceAnalysisProgress (audioSource, state, value); + audioSource->notifyListeners ([&] (ARAAudioSource::Listener& l) { l.didUpdateAudioSourceAnalysisProgress (audioSource, state, value); }); + } + + static void ARA_CALL notifyAudioSourceContentChanged (ARAModelUpdateControllerHostRef, ARAAudioSourceHostRef, + const ARAContentTimeRange*, ARAContentUpdateFlags) noexcept + { + jassertfalse; // not to be called - this adapter only forwards analysis progress + } + + static void ARA_CALL notifyAudioModificationContentChanged (ARAModelUpdateControllerHostRef, ARAAudioModificationHostRef, + const ARAContentTimeRange*, ARAContentUpdateFlags) noexcept + { + jassertfalse; // not to be called - this adapter only forwards analysis progress + } + + static void ARA_CALL notifyPlaybackRegionContentChanged (ARAModelUpdateControllerHostRef, ARAPlaybackRegionHostRef, + const ARAContentTimeRange*, ARAContentUpdateFlags) noexcept + { + jassertfalse; // not to be called - this adapter only forwards analysis progress + } + + static ARA::PlugIn::HostModelUpdateController* get() + { + static const auto modelUpdateControllerInterface = makeARASizedStruct (&ARA::ARAModelUpdateControllerInterface::notifyPlaybackRegionContentChanged, + ModelUpdateControllerProgressAdapter::notifyAudioSourceAnalysisProgress, + ModelUpdateControllerProgressAdapter::notifyAudioSourceContentChanged, + ModelUpdateControllerProgressAdapter::notifyAudioModificationContentChanged, + ModelUpdateControllerProgressAdapter::notifyPlaybackRegionContentChanged); + + static const auto instance = makeARASizedStruct (&ARA::ARADocumentControllerHostInstance::playbackControllerInterface, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + &modelUpdateControllerInterface, + nullptr, + nullptr); + + static auto progressAdapter = ARA::PlugIn::HostModelUpdateController { &instance }; + return &progressAdapter; + } +} + +void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::timerCallback() +{ + if (! internalAnalysisProgressIsSynced.exchange (true, std::memory_order_release)) + for (auto& audioSource : getDocument()->getAudioSources()) + audioSource->internalAnalysisProgressTracker.notifyProgress (ModelUpdateControllerProgressAdapter::get(), + reinterpret_cast (audioSource)); +} + +//============================================================================== +ARAInputStream::ARAInputStream (ARA::PlugIn::HostArchiveReader* reader) + : archiveReader (reader), + size ((int64) reader->getArchiveSize()) +{} + +int ARAInputStream::read (void* destBuffer, int maxBytesToRead) +{ + const auto bytesToRead = std::min ((int64) maxBytesToRead, size - position); + + if (bytesToRead > 0 && ! archiveReader->readBytesFromArchive ((ARA::ARASize) position, (ARA::ARASize) bytesToRead, + static_cast (destBuffer))) + { + failure = true; + return 0; + } + + position += bytesToRead; + return (int) bytesToRead; +} + +bool ARAInputStream::setPosition (int64 newPosition) +{ + position = jlimit ((int64) 0, size, newPosition); + return true; +} + +bool ARAInputStream::isExhausted() +{ + return position >= size; +} + +ARAOutputStream::ARAOutputStream (ARA::PlugIn::HostArchiveWriter* writer) + : archiveWriter (writer) +{} + +bool ARAOutputStream::write (const void* dataToWrite, size_t numberOfBytes) +{ + if (! archiveWriter->writeBytesToArchive ((ARA::ARASize) position, numberOfBytes, (const ARA::ARAByte*) dataToWrite)) + return false; + + position += numberOfBytes; + return true; +} + +bool ARAOutputStream::setPosition (int64 newPosition) +{ + position = newPosition; + return true; +} + +//============================================================================== +ARADocumentControllerSpecialisation::ARADocumentControllerSpecialisation ( + const ARA::PlugIn::PlugInEntry* entry, + const ARA::ARADocumentControllerHostInstance* instance) + : documentController (std::make_unique (entry, instance, this)) +{ +} + +ARADocumentControllerSpecialisation::~ARADocumentControllerSpecialisation() = default; + +ARAPlaybackRenderer* ARADocumentControllerSpecialisation::doCreatePlaybackRenderer() +{ + return new ARAPlaybackRenderer (getDocumentController()); +} + +ARAEditorRenderer* ARADocumentControllerSpecialisation::doCreateEditorRenderer() +{ + return new ARAEditorRenderer (getDocumentController()); +} + +ARAEditorView* ARADocumentControllerSpecialisation::doCreateEditorView() +{ + return new ARAEditorView (getDocumentController()); +} + +bool ARADocumentControllerSpecialisation::doIsAudioSourceContentAvailable (const ARA::PlugIn::AudioSource* audioSource, + ARA::ARAContentType type) +{ + juce::ignoreUnused (audioSource, type); + return false; +} + +ARA::ARAContentGrade ARADocumentControllerSpecialisation::doGetAudioSourceContentGrade (const ARA::PlugIn::AudioSource* audioSource, + ARA::ARAContentType type) +{ + // Overriding doIsAudioSourceContentAvailable() requires overriding + // doGetAudioSourceContentGrade() accordingly! + jassertfalse; + + juce::ignoreUnused (audioSource, type); + return ARA::kARAContentGradeInitial; +} + +ARA::PlugIn::ContentReader* ARADocumentControllerSpecialisation::doCreateAudioSourceContentReader (ARA::PlugIn::AudioSource* audioSource, + ARA::ARAContentType type, + const ARA::ARAContentTimeRange* range) +{ + // Overriding doIsAudioSourceContentAvailable() requires overriding + // doCreateAudioSourceContentReader() accordingly! + jassertfalse; + + juce::ignoreUnused (audioSource, type, range); + return nullptr; +} + +bool ARADocumentControllerSpecialisation::doIsAudioModificationContentAvailable (const ARA::PlugIn::AudioModification* audioModification, + ARA::ARAContentType type) +{ + juce::ignoreUnused (audioModification, type); + return false; +} + +ARA::ARAContentGrade ARADocumentControllerSpecialisation::doGetAudioModificationContentGrade (const ARA::PlugIn::AudioModification* audioModification, + ARA::ARAContentType type) +{ + // Overriding doIsAudioModificationContentAvailable() requires overriding + // doGetAudioModificationContentGrade() accordingly! + jassertfalse; + + juce::ignoreUnused (audioModification, type); + return ARA::kARAContentGradeInitial; +} + +ARA::PlugIn::ContentReader* ARADocumentControllerSpecialisation::doCreateAudioModificationContentReader (ARA::PlugIn::AudioModification* audioModification, + ARA::ARAContentType type, + const ARA::ARAContentTimeRange* range) +{ + // Overriding doIsAudioModificationContentAvailable() requires overriding + // doCreateAudioModificationContentReader() accordingly! + jassertfalse; + + juce::ignoreUnused (audioModification, type, range); + return nullptr; +} + +bool ARADocumentControllerSpecialisation::doIsPlaybackRegionContentAvailable (const ARA::PlugIn::PlaybackRegion* playbackRegion, + ARA::ARAContentType type) +{ + juce::ignoreUnused (playbackRegion, type); + return false; +} + +ARA::ARAContentGrade ARADocumentControllerSpecialisation::doGetPlaybackRegionContentGrade (const ARA::PlugIn::PlaybackRegion* playbackRegion, + ARA::ARAContentType type) +{ + // Overriding doIsPlaybackRegionContentAvailable() requires overriding + // doGetPlaybackRegionContentGrade() accordingly! + jassertfalse; + + juce::ignoreUnused (playbackRegion, type); + return ARA::kARAContentGradeInitial; +} + +ARA::PlugIn::ContentReader* ARADocumentControllerSpecialisation::doCreatePlaybackRegionContentReader (ARA::PlugIn::PlaybackRegion* playbackRegion, + ARA::ARAContentType type, + const ARA::ARAContentTimeRange* range) +{ + // Overriding doIsPlaybackRegionContentAvailable() requires overriding + // doCreatePlaybackRegionContentReader() accordingly! + jassertfalse; + + juce::ignoreUnused (playbackRegion, type, range); + return nullptr; +} + +bool ARADocumentControllerSpecialisation::doIsAudioSourceContentAnalysisIncomplete (const ARA::PlugIn::AudioSource* audioSource, + ARA::ARAContentType type) +{ + juce::ignoreUnused (audioSource, type); + return false; +} + +void ARADocumentControllerSpecialisation::doRequestAudioSourceContentAnalysis (ARA::PlugIn::AudioSource* audioSource, + std::vector const& contentTypes) +{ + juce::ignoreUnused (audioSource, contentTypes); +} + +ARA::ARAInt32 ARADocumentControllerSpecialisation::doGetProcessingAlgorithmsCount() { return 0; } + +const ARA::ARAProcessingAlgorithmProperties* + ARADocumentControllerSpecialisation::doGetProcessingAlgorithmProperties (ARA::ARAInt32 algorithmIndex) +{ + juce::ignoreUnused (algorithmIndex); + return nullptr; +} + +ARA::ARAInt32 ARADocumentControllerSpecialisation::doGetProcessingAlgorithmForAudioSource (const ARA::PlugIn::AudioSource* audioSource) +{ + // doGetProcessingAlgorithmForAudioSource() must be implemented if the supported + // algorithm count is greater than zero. + if (getDocumentController()->getProcessingAlgorithmsCount() > 0) + jassertfalse; + + juce::ignoreUnused (audioSource); + return 0; +} + +void ARADocumentControllerSpecialisation::doRequestProcessingAlgorithmForAudioSource (ARA::PlugIn::AudioSource* audioSource, + ARA::ARAInt32 algorithmIndex) +{ + // doRequestProcessingAlgorithmForAudioSource() must be implemented if the supported + // algorithm count is greater than zero. + if (getDocumentController()->getProcessingAlgorithmsCount() > 0) + jassertfalse; + + juce::ignoreUnused (audioSource, algorithmIndex); +} + +} // namespace juce diff --git a/modules/juce_audio_processors/utilities/ARA/juce_ARADocumentController.h b/modules/juce_audio_processors/utilities/ARA/juce_ARADocumentController.h new file mode 100644 index 0000000000..c0d5dd4490 --- /dev/null +++ b/modules/juce_audio_processors/utilities/ARA/juce_ARADocumentController.h @@ -0,0 +1,513 @@ +/* + ============================================================================== + + This file is part of the JUCE 7 technical preview. + Copyright (c) 2022 - Raw Material Software Limited + + You may use this code under the terms of the GPL v3 + (see www.gnu.org/licenses). + + For the technical preview this file cannot be licensed commercially. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#pragma once + +namespace juce +{ + +class ARAPlaybackRenderer; +class ARAEditorRenderer; +class ARAEditorView; +class ARAInputStream; +class ARAOutputStream; + +/** This class contains the customisation points for the JUCE provided ARA document controller + implementation. + + Every ARA enabled plugin must provide its own document controller implementation. To do this, + inherit from this class, and override its protected methods as needed. Then you need to + implement a global function somewhere in your module called createARAFactory(). This function + must return an ARAFactory* that will instantiate document controller objects using your + specialisation. There are helper functions inside ARADocumentControllerSpecialisation, so the + implementation of createARAFactory() can always be a simple one-liner. For example + + @code + class MyDocumentController : public ARADocumentControllerSpecialisation + { + //... + }; + + const ARA::ARAFactory* JUCE_CALLTYPE createARAFactory() + { + return juce::ARADocumentControllerSpecialisation::createARAFactory(); + } + @endcode + + Most member functions have a default implementation so you can build up your required feature + set gradually. The protected functions of this class fall in three distinct groups: + - interactive editing and playback, + - analysis features provided by the plugin and utilised by the host, and + - maintaining the ARA model graph. + + On top of the pure virtual functions, you will probably want to override + doCreatePlaybackRenderer() at the very least if you want your plugin to play any sound. This + function belongs to the first group. + + If your plugin has analysis capabilities and wants to allow the host to access these, functions + in the second group should be overridden. + + The default implementation of the ARA model object classes - i.e. ARADocument, ARAMusicalContext, + ARARegionSequence, ARAAudioSource, ARAAudioModification, ARAPlaybackRegion - should be sufficient + for maintaining a representation of the ARA model graph, hence overriding the model object + creation functions e.g. doCreateMusicalContext() is considered an advanced use case. Hence you + should be able to get a lot done without overriding functions in the third group. + + In order to react to the various ARA state changes you can override any of the ARA model object + Listener functions that ARADocumentControllerSpecialisation inherits from. Such listener + functions can be attached to one particular model objects instance, but the listener functions + inside ARADocumentControllerSpecialisation will respond to the events of all instances of the + model objects. + + @tags{ARA} +*/ +class ARADocumentControllerSpecialisation : public ARADocument::Listener, + public ARAMusicalContext::Listener, + public ARARegionSequence::Listener, + public ARAAudioSource::Listener, + public ARAAudioModification::Listener, + public ARAPlaybackRegion::Listener +{ +public: + //============================================================================== + /** Constructor. Used internally by the ARAFactory implementation. + */ + ARADocumentControllerSpecialisation (const ARA::PlugIn::PlugInEntry* entry, + const ARA::ARADocumentControllerHostInstance* instance); + + /** Destructor. */ + virtual ~ARADocumentControllerSpecialisation(); + + /** Returns the underlying DocumentController object that references this specialisation. + */ + ARA::PlugIn::DocumentController* getDocumentController() noexcept; + + /** Helper function for implementing the global createARAFactory() function. + + For example + + @code + class MyDocumentController : public ARADocumentControllerSpecialisation + { + //... + }; + + const ARA::ARAFactory* JUCE_CALLTYPE createARAFactory() + { + return juce::ARADocumentControllerSpecialisation::createARAFactory(); + } + @endcode + */ + template + static const ARA::ARAFactory* createARAFactory() + { + static_assert (std::is_base_of::value, + "DocumentController specialization types must inherit from ARADocumentControllerSpecialisation"); + return ARA::PlugIn::PlugInEntry::getPlugInEntry>()->getFactory(); + } + + /** Returns a pointer to the ARADocumentControllerSpecialisation instance that is referenced + by the provided DocumentController. You can use this function to access your specialisation + from anywhere where you have access to ARA::PlugIn::DocumentController*. + */ + template + static Specialisation* getSpecialisedDocumentController (ARA::PlugIn::DocumentController* dc) + { + return static_cast (getSpecialisedDocumentControllerImpl (dc)); + } + + /** Returns a pointer to the ARA document root maintained by this document controller. */ + template + DocumentType* getDocument() + { + return static_cast (getDocumentImpl()); + } + +protected: + //============================================================================== + /** Read an ARADocument archive from a juce::InputStream. + + @param input Data stream containing previously persisted data to be used when restoring the ARADocument + @param filter A filter to be applied to the stream + + Return true if the operation is successful. + + @see ARADocumentControllerInterface::restoreObjectsFromArchive + */ + virtual bool doRestoreObjectsFromStream (ARAInputStream& input, const ARARestoreObjectsFilter* filter) = 0; + + /** Write an ARADocument archive to a juce::OutputStream. + + @param output Data stream that should be used to write the persistent ARADocument data + @param filter A filter to be applied to the stream + + Returns true if the operation is successful. + + @see ARADocumentControllerInterface::storeObjectsToArchive + */ + virtual bool doStoreObjectsToStream (ARAOutputStream& output, const ARAStoreObjectsFilter* filter) = 0; + + //============================================================================== + /** Override to return a custom subclass instance of ARAPlaybackRenderer. */ + virtual ARAPlaybackRenderer* doCreatePlaybackRenderer(); + + /** Override to return a custom subclass instance of ARAEditorRenderer. */ + virtual ARAEditorRenderer* doCreateEditorRenderer(); + + /** Override to return a custom subclass instance of ARAEditorView. */ + virtual ARAEditorView* doCreateEditorView(); + + //============================================================================== + // ARAAudioSource content access + + /** Override to implement isAudioSourceContentAvailable() for all your supported content types - + the default implementation always returns false, preventing any calls to doGetAudioSourceContentGrade() + and doCreateAudioSourceContentReader(). + + This function's result is returned from + ARA::PlugIn::DocumentControllerDelegate::doIsAudioSourceContentAvailable. + */ + virtual bool doIsAudioSourceContentAvailable (const ARA::PlugIn::AudioSource* audioSource, + ARA::ARAContentType type); + + /** Override to implement getAudioSourceContentGrade() for all your supported content types. + + This function's result is returned from + ARA::PlugIn::DocumentControllerDelegate::doGetAudioSourceContentGrade. + */ + virtual ARA::ARAContentGrade doGetAudioSourceContentGrade (const ARA::PlugIn::AudioSource* audioSource, + ARA::ARAContentType type); + + /** Override to implement createAudioSourceContentReader() for all your supported content types, + returning a custom subclass instance of ContentReader providing data of the requested type. + + This function's result is returned from + ARA::PlugIn::DocumentControllerDelegate::doCreateAudioSourceContentReader. + */ + virtual ARA::PlugIn::ContentReader* doCreateAudioSourceContentReader (ARA::PlugIn::AudioSource* audioSource, + ARA::ARAContentType type, + const ARA::ARAContentTimeRange* range); + + //============================================================================== + // ARAAudioModification content access + + /** Override to implement isAudioModificationContentAvailable() for all your supported content types - + the default implementation always returns false. + For read-only data directly inherited from the underlying audio source you can just delegate the + call to the audio source, but user-editable modification data must be specifically handled here. + + This function's result is returned from + ARA::PlugIn::DocumentControllerDelegate::doIsAudioModificationContentAvailable. + */ + virtual bool doIsAudioModificationContentAvailable (const ARA::PlugIn::AudioModification* audioModification, + ARA::ARAContentType type); + + /** Override to implement getAudioModificationContentGrade() for all your supported content types. + For read-only data directly inherited from the underlying audio source you can just delegate the + call to the audio source, but user-editable modification data must be specifically handled here. + + This function's result is returned from + ARA::PlugIn::DocumentControllerDelegate::doGetAudioModificationContentGrade. + */ + virtual ARA::ARAContentGrade doGetAudioModificationContentGrade (const ARA::PlugIn::AudioModification* audioModification, + ARA::ARAContentType type); + + /** Override to implement createAudioModificationContentReader() for all your supported content types, + returning a custom subclass instance of ContentReader providing data of the requested \p type. + For read-only data directly inherited from the underlying audio source you can just delegate the + call to the audio source, but user-editable modification data must be specifically handled here. + + This function's result is returned from + ARA::PlugIn::DocumentControllerDelegate::doCreateAudioModificationContentReader. + */ + virtual ARA::PlugIn::ContentReader* doCreateAudioModificationContentReader (ARA::PlugIn::AudioModification* audioModification, + ARA::ARAContentType type, + const ARA::ARAContentTimeRange* range); + + //============================================================================== + // ARAPlaybackRegion content access + + /** Override to implement isPlaybackRegionContentAvailable() for all your supported content types - + the default implementation always returns false. + Typically, this call can directly delegate to the underlying audio modification, since most + plug-ins will apply their modification data to the playback region with a transformation that + does not affect content availability. + + This function's result is returned from + ARA::PlugIn::DocumentControllerDelegate::doIsPlaybackRegionContentAvailable. + */ + virtual bool doIsPlaybackRegionContentAvailable (const ARA::PlugIn::PlaybackRegion* playbackRegion, + ARA::ARAContentType type); + + /** Override to implement getPlaybackRegionContentGrade() for all your supported content types. + Typically, this call can directly delegate to the underlying audio modification, since most + plug-ins will apply their modification data to the playback region with a transformation that + does not affect content grade. + + This function's result is returned from + ARA::PlugIn::DocumentControllerDelegate::doGetPlaybackRegionContentGrade. + */ + virtual ARA::ARAContentGrade doGetPlaybackRegionContentGrade (const ARA::PlugIn::PlaybackRegion* playbackRegion, + ARA::ARAContentType type); + + /** Override to implement createPlaybackRegionContentReader() for all your supported content types, + returning a custom subclass instance of ContentReader providing data of the requested type. + + This function's result is returned from + ARA::PlugIn::DocumentControllerDelegate::doCreatePlaybackRegionContentReader. + */ + virtual ARA::PlugIn::ContentReader* doCreatePlaybackRegionContentReader (ARA::PlugIn::PlaybackRegion* playbackRegion, + ARA::ARAContentType type, + const ARA::ARAContentTimeRange* range); + + //============================================================================== + // ARAAudioSource analysis + + /** Override to implement isAudioSourceContentAnalysisIncomplete(). + + This function's result is returned from + ARA::PlugIn::DocumentControllerDelegate::doIsAudioSourceContentAnalysisIncomplete. + */ + virtual bool doIsAudioSourceContentAnalysisIncomplete (const ARA::PlugIn::AudioSource* audioSource, + ARA::ARAContentType type); + + /** Override to implement requestAudioSourceContentAnalysis(). + + This function's called from + ARA::PlugIn::DocumentControllerDelegate::doRequestAudioSourceContentAnalysis. + */ + virtual void doRequestAudioSourceContentAnalysis (ARA::PlugIn::AudioSource* audioSource, + std::vector const& contentTypes); + + //============================================================================== + // Analysis Algorithm selection + + /** Override to implement getProcessingAlgorithmsCount(). + + This function's result is returned from + ARA::PlugIn::DocumentControllerDelegate::doGetProcessingAlgorithmsCount. + */ + virtual ARA::ARAInt32 doGetProcessingAlgorithmsCount (); + + /** Override to implement getProcessingAlgorithmProperties(). + + This function's result is returned from + ARA::PlugIn::DocumentControllerDelegate::doGetProcessingAlgorithmProperties. + */ + virtual const ARA::ARAProcessingAlgorithmProperties* + doGetProcessingAlgorithmProperties (ARA::ARAInt32 algorithmIndex); + + /** Override to implement getProcessingAlgorithmForAudioSource(). + + This function's result is returned from + ARA::PlugIn::DocumentControllerDelegate::doGetProcessingAlgorithmForAudioSource. + */ + virtual ARA::ARAInt32 doGetProcessingAlgorithmForAudioSource (const ARA::PlugIn::AudioSource* audioSource); + + /** Override to implement requestProcessingAlgorithmForAudioSource(). + + This function's called from + ARA::PlugIn::DocumentControllerDelegate::doRequestProcessingAlgorithmForAudioSource. + */ + virtual void doRequestProcessingAlgorithmForAudioSource (ARA::PlugIn::AudioSource* audioSource, ARA::ARAInt32 algorithmIndex); + + //============================================================================== + /** Override to return a custom subclass instance of ARADocument. */ + ARADocument* doCreateDocument(); + + /** Override to return a custom subclass instance of ARAMusicalContext. */ + ARAMusicalContext* doCreateMusicalContext (ARADocument* document, + ARA::ARAMusicalContextHostRef hostRef); + + /** Override to return a custom subclass instance of ARARegionSequence. */ + ARARegionSequence* doCreateRegionSequence (ARADocument* document, + ARA::ARARegionSequenceHostRef hostRef); + + /** Override to return a custom subclass instance of ARAAudioSource. */ + ARAAudioSource* doCreateAudioSource (ARADocument* document, + ARA::ARAAudioSourceHostRef hostRef); + + /** Override to return a custom subclass instance of ARAAudioModification. */ + ARAAudioModification* doCreateAudioModification (ARAAudioSource* audioSource, + ARA::ARAAudioModificationHostRef hostRef, + const ARAAudioModification* optionalModificationToClone); + + /** Override to return a custom subclass instance of ARAPlaybackRegion. */ + ARAPlaybackRegion* doCreatePlaybackRegion (ARAAudioModification* modification, + ARA::ARAPlaybackRegionHostRef hostRef); + +private: + //============================================================================== + template + class FactoryConfig : public ARA::PlugIn::FactoryConfig + { + public: + FactoryConfig() noexcept + { + const juce::String compatibleDocumentArchiveIDString = JucePlugin_ARACompatibleArchiveIDs; + + if (compatibleDocumentArchiveIDString.isNotEmpty()) + { + compatibleDocumentArchiveIDStrings = juce::StringArray::fromLines (compatibleDocumentArchiveIDString); + for (const auto& compatibleID : compatibleDocumentArchiveIDStrings) + compatibleDocumentArchiveIDs.push_back (compatibleID.toRawUTF8()); + } + + // Update analyzeable content types + static constexpr std::array contentTypes { + ARA::kARAContentTypeNotes, + ARA::kARAContentTypeTempoEntries, + ARA::kARAContentTypeBarSignatures, + ARA::kARAContentTypeStaticTuning, + ARA::kARAContentTypeKeySignatures, + ARA::kARAContentTypeSheetChords + }; + + JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6313) + + for (size_t i = 0; i < contentTypes.size(); ++i) + if (JucePlugin_ARAContentTypes & (1 << i)) + analyzeableContentTypes.push_back (contentTypes[i]); + + JUCE_END_IGNORE_WARNINGS_MSVC + + // Update playback transformation flags + static constexpr std::array playbackTransformationFlags { + ARA::kARAPlaybackTransformationTimestretch, + ARA::kARAPlaybackTransformationTimestretchReflectingTempo, + ARA::kARAPlaybackTransformationContentBasedFadeAtTail, + ARA::kARAPlaybackTransformationContentBasedFadeAtHead + }; + + supportedPlaybackTransformationFlags = 0; + + JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6313) + + for (size_t i = 0; i < playbackTransformationFlags.size(); ++i) + if (JucePlugin_ARATransformationFlags & (1 << i)) + supportedPlaybackTransformationFlags |= playbackTransformationFlags[i]; + + JUCE_END_IGNORE_WARNINGS_MSVC + } + + const char* getFactoryID() const noexcept override { return JucePlugin_ARAFactoryID; } + const char* getPlugInName() const noexcept override { return JucePlugin_Name; } + const char* getManufacturerName() const noexcept override { return JucePlugin_Manufacturer; } + const char* getInformationURL() const noexcept override { return JucePlugin_ManufacturerWebsite; } + const char* getVersion() const noexcept override { return JucePlugin_VersionString; } + const char* getDocumentArchiveID() const noexcept override { return JucePlugin_ARADocumentArchiveID; } + + ARA::ARASize getCompatibleDocumentArchiveIDsCount() const noexcept override + { + return compatibleDocumentArchiveIDs.size(); + } + + const ARA::ARAPersistentID* getCompatibleDocumentArchiveIDs() const noexcept override + { + return compatibleDocumentArchiveIDs.empty() ? nullptr : compatibleDocumentArchiveIDs.data(); + } + + ARA::ARASize getAnalyzeableContentTypesCount() const noexcept override + { + return analyzeableContentTypes.size(); + } + + const ARA::ARAContentType* getAnalyzeableContentTypes() const noexcept override + { + return analyzeableContentTypes.empty() ? nullptr : analyzeableContentTypes.data(); + } + + ARA::ARAPlaybackTransformationFlags getSupportedPlaybackTransformationFlags() const noexcept override + { + return supportedPlaybackTransformationFlags; + } + + ARA::PlugIn::DocumentController* createDocumentController (const ARA::PlugIn::PlugInEntry* entry, + const ARA::ARADocumentControllerHostInstance* instance) const noexcept override + { + auto* spec = new SpecialisationType (entry, instance); + return spec->getDocumentController(); + } + + void destroyDocumentController (ARA::PlugIn::DocumentController* controller) const noexcept override + { + delete getSpecialisedDocumentController (controller); + } + + private: + juce::StringArray compatibleDocumentArchiveIDStrings; + std::vector compatibleDocumentArchiveIDs; + std::vector analyzeableContentTypes; + ARA::ARAPlaybackTransformationFlags supportedPlaybackTransformationFlags; + }; + + //============================================================================== + static ARADocumentControllerSpecialisation* getSpecialisedDocumentControllerImpl (ARA::PlugIn::DocumentController*); + + ARADocument* getDocumentImpl(); + + //============================================================================== + class ARADocumentControllerImpl; + std::unique_ptr documentController; +}; + +/** Used to read persisted ARA archives - see doRestoreObjectsFromStream() for details. + + @tags{ARA} +*/ +class ARAInputStream : public InputStream +{ +public: + explicit ARAInputStream (ARA::PlugIn::HostArchiveReader*); + + int64 getPosition() override { return position; } + int64 getTotalLength() override { return size; } + + int read (void*, int) override; + bool setPosition (int64) override; + bool isExhausted() override; + + bool failed() const { return failure; } + +private: + ARA::PlugIn::HostArchiveReader* archiveReader; + int64 position = 0; + int64 size; + bool failure = false; +}; + +/** Used to write persistent ARA archives - see doStoreObjectsToStream() for details. + + @tags{ARA} +*/ +class ARAOutputStream : public OutputStream +{ +public: + explicit ARAOutputStream (ARA::PlugIn::HostArchiveWriter*); + + int64 getPosition() override { return position; } + void flush() override {} + + bool write (const void*, size_t) override; + bool setPosition (int64) override; + +private: + ARA::PlugIn::HostArchiveWriter* archiveWriter; + int64 position = 0; +}; +} // namespace juce diff --git a/modules/juce_audio_processors/utilities/ARA/juce_ARADocumentControllerCommon.cpp b/modules/juce_audio_processors/utilities/ARA/juce_ARADocumentControllerCommon.cpp new file mode 100644 index 0000000000..0874691eb9 --- /dev/null +++ b/modules/juce_audio_processors/utilities/ARA/juce_ARADocumentControllerCommon.cpp @@ -0,0 +1,64 @@ +/* + ============================================================================== + + This file is part of the JUCE 7 technical preview. + Copyright (c) 2022 - Raw Material Software Limited + + You may use this code under the terms of the GPL v3 + (see www.gnu.org/licenses). + + For the technical preview this file cannot be licensed commercially. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +class ARADocumentController : public ARA::PlugIn::DocumentController +{ +public: + using ARA::PlugIn::DocumentController::DocumentController; + + template + Document_t* getDocument() const noexcept { return ARA::PlugIn::DocumentController::getDocument(); } + + //============================================================================== + /** @internal */ + virtual void internalNotifyAudioSourceAnalysisProgressStarted (ARAAudioSource* audioSource) = 0; + + /** @internal */ + virtual void internalNotifyAudioSourceAnalysisProgressUpdated (ARAAudioSource* audioSource, float progress) = 0; + + /** @internal */ + virtual void internalNotifyAudioSourceAnalysisProgressCompleted (ARAAudioSource* audioSource) = 0; + + /** @internal */ + virtual void internalDidUpdateAudioSourceAnalysisProgress (ARAAudioSource* audioSource, + ARAAudioSource::ARAAnalysisProgressState state, + float progress) = 0; + + //============================================================================== + /** @internal */ + virtual void internalNotifyAudioSourceContentChanged (ARAAudioSource* audioSource, + ARAContentUpdateScopes scopeFlags, + bool notifyARAHost) = 0; + + /** @internal */ + virtual void internalNotifyAudioModificationContentChanged (ARAAudioModification* audioModification, + ARAContentUpdateScopes scopeFlags, + bool notifyARAHost) = 0; + + /** @internal */ + virtual void internalNotifyPlaybackRegionContentChanged (ARAPlaybackRegion* playbackRegion, + ARAContentUpdateScopes scopeFlags, + bool notifyARAHost) = 0; + + friend class ARAPlaybackRegionReader; +}; + +} // namespace juce diff --git a/modules/juce_audio_processors/utilities/ARA/juce_ARAModelObjects.cpp b/modules/juce_audio_processors/utilities/ARA/juce_ARAModelObjects.cpp new file mode 100644 index 0000000000..e436447f0a --- /dev/null +++ b/modules/juce_audio_processors/utilities/ARA/juce_ARAModelObjects.cpp @@ -0,0 +1,192 @@ +/* + ============================================================================== + + This file is part of the JUCE 7 technical preview. + Copyright (c) 2022 - Raw Material Software Limited + + You may use this code under the terms of the GPL v3 + (see www.gnu.org/licenses). + + For the technical preview this file cannot be licensed commercially. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +size_t ARADocument::getNumChildren() const noexcept +{ + return getMusicalContexts().size() + getRegionSequences().size() + getAudioSources().size(); +} + +ARAObject* ARADocument::getChild (size_t index) +{ + auto& musicalContexts = getMusicalContexts(); + + if (index < musicalContexts.size()) + return musicalContexts[index]; + + const auto numMusicalContexts = musicalContexts.size(); + auto& regionSequences = getRegionSequences(); + + if (index < numMusicalContexts + regionSequences.size()) + return regionSequences[index - numMusicalContexts]; + + const auto numMusicalContextsAndRegionSequences = numMusicalContexts + regionSequences.size(); + auto& audioSources = getAudioSources(); + + if (index < numMusicalContextsAndRegionSequences + audioSources.size()) + return getAudioSources()[index - numMusicalContextsAndRegionSequences]; + + return nullptr; +} + +//============================================================================== +size_t ARARegionSequence::getNumChildren() const noexcept +{ + return 0; +} + +ARAObject* ARARegionSequence::getChild (size_t) +{ + return nullptr; +} + +Range ARARegionSequence::getTimeRange (ARAPlaybackRegion::IncludeHeadAndTail includeHeadAndTail) const +{ + if (getPlaybackRegions().empty()) + return {}; + + auto startTime = std::numeric_limits::max(); + auto endTime = std::numeric_limits::lowest(); + for (const auto& playbackRegion : getPlaybackRegions()) + { + const auto regionTimeRange = playbackRegion->getTimeRange (includeHeadAndTail); + startTime = jmin (startTime, regionTimeRange.getStart()); + endTime = jmax (endTime, regionTimeRange.getEnd()); + } + return { startTime, endTime }; +} + +double ARARegionSequence::getCommonSampleRate() const +{ + const auto getSampleRate = [] (auto* playbackRegion) + { + return playbackRegion->getAudioModification()->getAudioSource()->getSampleRate(); + }; + + const auto range = getPlaybackRegions(); + const auto sampleRate = range.size() > 0 ? getSampleRate (range.front()) : 0.0; + + if (std::any_of (range.begin(), range.end(), [&] (auto& x) { return getSampleRate (x) != sampleRate; })) + return 0.0; + + return sampleRate; +} + +//============================================================================== +size_t ARAAudioSource::getNumChildren() const noexcept +{ + return getAudioModifications().size(); +} + +ARAObject* ARAAudioSource::getChild (size_t index) +{ + return getAudioModifications()[index]; +} + +void ARAAudioSource::notifyAnalysisProgressStarted() +{ + getDocumentController()->internalNotifyAudioSourceAnalysisProgressStarted (this); +} + +void ARAAudioSource::notifyAnalysisProgressUpdated (float progress) +{ + getDocumentController()->internalNotifyAudioSourceAnalysisProgressUpdated (this, progress); +} + +void ARAAudioSource::notifyAnalysisProgressCompleted() +{ + getDocumentController()->internalNotifyAudioSourceAnalysisProgressCompleted (this); +} + +void ARAAudioSource::notifyContentChanged (ARAContentUpdateScopes scopeFlags, bool notifyARAHost) +{ + getDocumentController()->internalNotifyAudioSourceContentChanged (this, + scopeFlags, + notifyARAHost); +} + +//============================================================================== +size_t ARAAudioModification::getNumChildren() const noexcept +{ + return getPlaybackRegions().size(); +} + +ARAObject* ARAAudioModification::getChild (size_t index) +{ + return getPlaybackRegions()[index]; +} + +void ARAAudioModification::notifyContentChanged (ARAContentUpdateScopes scopeFlags, bool notifyARAHost) +{ + getDocumentController()->internalNotifyAudioModificationContentChanged (this, + scopeFlags, + notifyARAHost); +} + +//============================================================================== +ARAObject* ARAPlaybackRegion::getParent() { return getAudioModification(); } + +Range ARAPlaybackRegion::getTimeRange (IncludeHeadAndTail includeHeadAndTail) const +{ + auto startTime = getStartInPlaybackTime(); + auto endTime = getEndInPlaybackTime(); + + if (includeHeadAndTail == IncludeHeadAndTail::yes) + { + ARA::ARATimeDuration headTime {}, tailTime {}; + getDocumentController()->getPlaybackRegionHeadAndTailTime (toRef (this), &headTime, &tailTime); + startTime -= headTime; + endTime += tailTime; + } + + return { startTime, endTime }; +} + +Range ARAPlaybackRegion::getSampleRange (double sampleRate, IncludeHeadAndTail includeHeadAndTail) const +{ + const auto timeRange = getTimeRange (includeHeadAndTail); + + return { ARA::samplePositionAtTime (timeRange.getStart(), sampleRate), + ARA::samplePositionAtTime (timeRange.getEnd(), sampleRate) }; +} + +double ARAPlaybackRegion::getHeadTime() const +{ + ARA::ARATimeDuration headTime {}, tailTime {}; + getDocumentController()->getPlaybackRegionHeadAndTailTime (toRef (this), &headTime, &tailTime); + return headTime; +} + +double ARAPlaybackRegion::getTailTime() const +{ + ARA::ARATimeDuration headTime {}, tailTime {}; + getDocumentController()->getPlaybackRegionHeadAndTailTime (toRef (this), &headTime, &tailTime); + return tailTime; +} + +void ARAPlaybackRegion::notifyContentChanged (ARAContentUpdateScopes scopeFlags, bool notifyARAHost) +{ + getDocumentController()->internalNotifyPlaybackRegionContentChanged (this, + scopeFlags, + notifyARAHost); +} + +} // namespace juce diff --git a/modules/juce_audio_processors/utilities/ARA/juce_ARAModelObjects.h b/modules/juce_audio_processors/utilities/ARA/juce_ARAModelObjects.h new file mode 100644 index 0000000000..070caabd0d --- /dev/null +++ b/modules/juce_audio_processors/utilities/ARA/juce_ARAModelObjects.h @@ -0,0 +1,1138 @@ +/* + ============================================================================== + + This file is part of the JUCE 7 technical preview. + Copyright (c) 2022 - Raw Material Software Limited + + You may use this code under the terms of the GPL v3 + (see www.gnu.org/licenses). + + For the technical preview this file cannot be licensed commercially. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#pragma once + +namespace juce +{ + +class ARADocumentController; +class ARADocument; +class ARAMusicalContext; +class ARARegionSequence; +class ARAAudioSource; +class ARAAudioModification; +class ARAPlaybackRegion; + +/** Base class used by the JUCE ARA model objects to provide listenable interfaces. + + @tags{ARA} +*/ +template +class JUCE_API ARAListenableModelClass +{ +public: + /** Constructor. */ + ARAListenableModelClass() = default; + + /** Destructor. */ + virtual ~ARAListenableModelClass() = default; + + /** Subscribe \p l to notified by changes to the object. + @param l The listener instance. + */ + void addListener (ListenerType* l) { listeners.add (l); } + + /** Unsubscribe \p l from object notifications. + @param l The listener instance. + */ + void removeListener (ListenerType* l) { listeners.remove (l); } + + /** Call the provided callback for each of the added listeners. */ + template + void notifyListeners (Callback&& callback) + { + listeners.call (callback); + } + +private: + ListenerList listeners; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARAListenableModelClass) +}; + +/** Create a derived implementation of this class and pass it to ARAObject::visit() to retrieve the + concrete type of a model object. + + Combined with ARAObject::traverse() on the ARADocument object it is possible to discover the + entire model graph. + + Note that the references passed to the visit member functions are only guaranteed to live for + the duration of the function call, so don't store pointers to these objects! + + @tags{Audio} +*/ +class ARAObjectVisitor +{ +public: + /** Destructor. */ + virtual ~ARAObjectVisitor() = default; + + /** Called when visiting an ARADocument object. */ + virtual void visitDocument (juce::ARADocument&) {} + + /** Called when visiting an ARAMusicalContext object. */ + virtual void visitMusicalContext (juce::ARAMusicalContext&) {} + + /** Called when visiting an ARARegionSequence object. */ + virtual void visitRegionSequence (juce::ARARegionSequence&) {} + + /** Called when visiting an ARAPlaybackRegion object. */ + virtual void visitPlaybackRegion (juce::ARAPlaybackRegion&) {} + + /** Called when visiting an ARAAudioModification object. */ + virtual void visitAudioModification (juce::ARAAudioModification&) {} + + /** Called when visiting an ARAAudioSource object. */ + virtual void visitAudioSource (juce::ARAAudioSource&) {} +}; + +/** Common base class for all JUCE ARA model objects to aid with the discovery and traversal of the + entire ARA model graph. + + @tags{ARA} +*/ +class ARAObject +{ +public: + /** Destructor. */ + virtual ~ARAObject() = default; + + /** Returns the number of ARA model objects aggregated by this object. Objects that are merely + referred to, but not aggregated by the current object are not included in this count, e.g. + a referenced RegionSequence does not count as a child of the referring PlaybackRegion. + + See the ARA documentation's ARA Model Graph Overview for more details. + */ + virtual size_t getNumChildren() const noexcept = 0; + + /** Returns the child object associated with the given index. + + The index should be smaller than the value returned by getNumChildren(). + + Note that the index of a particular object may change when the ARA model graph is edited. + */ + virtual ARAObject* getChild (size_t index) = 0; + + /** Returns the ARA model object that aggregates this object. + + Returns nullptr for the ARADocument root object. + */ + virtual ARAObject* getParent() = 0; + + /** Implements a depth first traversal of the ARA model graph starting from the current object, + and visiting its children recursively. + + The provided function should accept a single ARAObject& parameter. + */ + template + void traverse (Fn&& fn) + { + fn (*this); + + for (size_t i = 0; i < getNumChildren(); ++i) + { + getChild (i)->traverse (fn); + } + } + + /** Allows the retrieval of the concrete type of a model object. + + To use this, create a new class derived from ARAObjectVisitor and override its functions + depending on which concrete types you are interested in. + + Calling this function inside the function passed to ARAObject::traverse() allows you to + map the entire ARA model graph. + */ + virtual void visit (ARAObjectVisitor& visitor) = 0; +}; + +/** A base class for listeners that want to know about changes to an ARADocument object. + + Use ARADocument::addListener() to register your listener with an ARADocument. + + @tags{ARA} +*/ +class JUCE_API ARADocumentListener +{ +public: + /** Destructor */ + virtual ~ARADocumentListener() = default; + + ARA_DISABLE_UNREFERENCED_PARAMETER_WARNING_BEGIN + + /** Called before the document enters an editing state. + @param document The document about to enter an editing state. + */ + virtual void willBeginEditing (ARADocument* document) + { + ignoreUnused (document); + } + + /** Called after the document exits an editing state. + @param document The document about exit an editing state. + */ + virtual void didEndEditing (ARADocument* document) + { + ignoreUnused (document); + } + + /** Called before sending model updates do the host. + @param document The document whose model updates are about to be sent. + */ + virtual void willNotifyModelUpdates (ARADocument* document) + { + ignoreUnused (document); + } + + /** Called after sending model updates do the host. + @param document The document whose model updates have just been sent. + */ + virtual void didNotifyModelUpdates (ARADocument* document) + { + ignoreUnused (document); + } + + /** Called before the document's properties are updated. + @param document The document whose properties will be updated. + @param newProperties The document properties that will be assigned to \p document. + */ + virtual void willUpdateDocumentProperties (ARADocument* document, + ARA::PlugIn::PropertiesPtr newProperties) + { + ignoreUnused (document, newProperties); + } + + /** Called after the document's properties are updated. + @param document The document whose properties were updated. + */ + virtual void didUpdateDocumentProperties (ARADocument* document) + { + ignoreUnused (document); + } + + /** Called after a musical context is added to the document. + @param document The document that \p musicalContext was added to. + @param musicalContext The musical context that was added to \p document. + */ + virtual void didAddMusicalContextToDocument (ARADocument* document, ARAMusicalContext* musicalContext) + { + ignoreUnused (document, musicalContext); + } + + /** Called before a musical context is removed from the document. + @param document The document that \p musicalContext will be removed from. + @param musicalContext The musical context that will be removed from \p document. + */ + virtual void willRemoveMusicalContextFromDocument (ARADocument* document, + ARAMusicalContext* musicalContext) + { + ignoreUnused (document, musicalContext); + } + + /** Called after the musical contexts are reordered in an ARA document + + Musical contexts are sorted by their order index - + this callback signals a change in this ordering within the document. + + @param document The document with reordered musical contexts. + */ + virtual void didReorderMusicalContextsInDocument (ARADocument* document) + { + ignoreUnused (document); + } + + /** Called after a region sequence is added to the document. + @param document The document that \p regionSequence was added to. + @param regionSequence The region sequence that was added to \p document. + */ + virtual void didAddRegionSequenceToDocument (ARADocument* document, ARARegionSequence* regionSequence) + { + ignoreUnused (document, regionSequence); + } + + /** Called before a region sequence is removed from the document. + @param document The document that \p regionSequence will be removed from. + @param regionSequence The region sequence that will be removed from \p document. + */ + virtual void willRemoveRegionSequenceFromDocument (ARADocument* document, + ARARegionSequence* regionSequence) + { + ignoreUnused (document, regionSequence); + } + + /** Called after the region sequences are reordered in an ARA document + + Region sequences are sorted by their order index - + this callback signals a change in this ordering within the document. + + @param document The document with reordered region sequences. + */ + virtual void didReorderRegionSequencesInDocument (ARADocument* document) + { + ignoreUnused (document); + } + + /** Called after an audio source is added to the document. + @param document The document that \p audioSource was added to. + @param audioSource The audio source that was added to \p document. + */ + virtual void didAddAudioSourceToDocument (ARADocument* document, ARAAudioSource* audioSource) + { + ignoreUnused (document, audioSource); + } + + /** Called before an audio source is removed from the document. + @param document The document that \p audioSource will be removed from . + @param audioSource The audio source that will be removed from \p document. + */ + virtual void willRemoveAudioSourceFromDocument (ARADocument* document, ARAAudioSource* audioSource) + { + ignoreUnused (document, audioSource); + } + + /** Called before the document is destroyed by the ARA host. + @param document The document that will be destroyed. + */ + virtual void willDestroyDocument (ARADocument* document) + { + ignoreUnused (document); + } + + ARA_DISABLE_UNREFERENCED_PARAMETER_WARNING_END +}; + +//============================================================================== +/** Base class representing an ARA document. + + @tags{ARA} +*/ +class JUCE_API ARADocument : public ARA::PlugIn::Document, + public ARAListenableModelClass, + public ARAObject +{ +public: + using PropertiesPtr = ARA::PlugIn::PropertiesPtr; + using Listener = ARADocumentListener; + + using ARA::PlugIn::Document::Document; + + /** Returns the result of ARA::PlugIn::Document::getAudioSources() with the pointers within + cast to ARAAudioSource*. + + If you have overridden ARADocumentControllerSpecialisation::doCreateAudioSource(), then + you can use the template parameter to cast the pointers to your subclass of ARAAudioSource. + */ + template + const std::vector& getAudioSources() const noexcept + { + return ARA::PlugIn::Document::getAudioSources(); + } + + /** Returns the result of ARA::PlugIn::Document::getMusicalContexts() with the pointers within + cast to ARAMusicalContext*. + + If you have overridden ARADocumentControllerSpecialisation::doCreateMusicalContext(), then + you can use the template parameter to cast the pointers to your subclass of ARAMusicalContext. + */ + template + const std::vector& getMusicalContexts() const noexcept + { + return ARA::PlugIn::Document::getMusicalContexts(); + } + + /** Returns the result of ARA::PlugIn::Document::getRegionSequences() with the pointers within + cast to ARARegionSequence*. + + If you have overridden ARADocumentControllerSpecialisation::doCreateRegionSequence(), then + you can use the template parameter to cast the pointers to your subclass of ARARegionSequence. + */ + template + const std::vector& getRegionSequences() const noexcept + { + return ARA::PlugIn::Document::getRegionSequences(); + } + + size_t getNumChildren() const noexcept override; + + ARAObject* getChild (size_t index) override; + + ARAObject* getParent() override { return nullptr; } + + void visit (ARAObjectVisitor& visitor) override { visitor.visitDocument (*this); } +}; + +/** A base class for listeners that want to know about changes to an ARAMusicalContext object. + + Use ARAMusicalContext::addListener() to register your listener with an ARAMusicalContext. + + @tags{ARA} +*/ +class JUCE_API ARAMusicalContextListener +{ +public: + virtual ~ARAMusicalContextListener() = default; + + ARA_DISABLE_UNREFERENCED_PARAMETER_WARNING_BEGIN + + /** Called before the musical context's properties are updated. + @param musicalContext The musical context whose properties will be updated. + @param newProperties The musical context properties that will be assigned to \p musicalContext. + */ + virtual void willUpdateMusicalContextProperties (ARAMusicalContext* musicalContext, + ARA::PlugIn::PropertiesPtr newProperties) + { + ignoreUnused (musicalContext, newProperties); + } + + /** Called after the musical context's properties are updated by the host. + @param musicalContext The musical context whose properties were updated. + */ + virtual void didUpdateMusicalContextProperties (ARAMusicalContext* musicalContext) + { + ignoreUnused (musicalContext); + } + + /** Called when the musical context's content (i.e tempo entries or chords) changes. + @param musicalContext The musical context with updated content. + @param scopeFlags The scope of the content update indicating what has changed. + */ + virtual void doUpdateMusicalContextContent (ARAMusicalContext* musicalContext, + ARAContentUpdateScopes scopeFlags) + { + ignoreUnused (musicalContext, scopeFlags); + } + + /** Called after a region sequence is added to the musical context. + @param musicalContext The musical context that \p regionSequence was added to. + @param regionSequence The region sequence that was added to \p musicalContext. + */ + virtual void didAddRegionSequenceToMusicalContext (ARAMusicalContext* musicalContext, + ARARegionSequence* regionSequence) + { + ignoreUnused (musicalContext, regionSequence); + } + + /** Called before a region sequence is removed from the musical context. + @param musicalContext The musical context that \p regionSequence will be removed from. + @param regionSequence The region sequence that will be removed from \p musicalContext. + */ + virtual void willRemoveRegionSequenceFromMusicalContext (ARAMusicalContext* musicalContext, + ARARegionSequence* regionSequence) + { + ignoreUnused (musicalContext, regionSequence); + } + + /** Called after the region sequences are reordered in an ARA MusicalContext + + Region sequences are sorted by their order index - + this callback signals a change in this ordering within the musical context. + + @param musicalContext The musical context with reordered region sequences. + */ + virtual void didReorderRegionSequencesInMusicalContext (ARAMusicalContext* musicalContext) + { + ignoreUnused (musicalContext); + } + + /** Called before the musical context is destroyed. + @param musicalContext The musical context that will be destoyed. + */ + virtual void willDestroyMusicalContext (ARAMusicalContext* musicalContext) + { + ignoreUnused (musicalContext); + } + + ARA_DISABLE_UNREFERENCED_PARAMETER_WARNING_END +}; + +//============================================================================== +/** Base class representing an ARA musical context. + + @tags{ARA} +*/ +class JUCE_API ARAMusicalContext : public ARA::PlugIn::MusicalContext, + public ARAListenableModelClass, + public ARAObject +{ +public: + using PropertiesPtr = ARA::PlugIn::PropertiesPtr; + using Listener = ARAMusicalContextListener; + + using ARA::PlugIn::MusicalContext::MusicalContext; + + /** Returns the result of ARA::PlugIn::MusicalContext::getDocument() with the pointer cast + to ARADocument*. + + If you have overridden ARADocumentControllerSpecialisation::doCreateDocument(), then you + can use the template parameter to cast the pointers to your subclass of ARADocument. + */ + template + Document_t* getDocument() const noexcept + { + return ARA::PlugIn::MusicalContext::getDocument(); + } + + /** Returns the result of ARA::PlugIn::MusicalContext::getRegionSequences() with the pointers + within cast to ARARegionSequence*. + + If you have overridden ARADocumentControllerSpecialisation::doCreateRegionSequence(), then you + can use the template parameter to cast the pointers to your subclass of ARARegionSequence. + */ + template + const std::vector& getRegionSequences() const noexcept + { + return ARA::PlugIn::MusicalContext::getRegionSequences(); + } + + size_t getNumChildren() const noexcept override { return 0; } + + ARAObject* getChild (size_t) override { return nullptr; } + + ARAObject* getParent() override { return getDocument(); } + + void visit (ARAObjectVisitor& visitor) override { visitor.visitMusicalContext (*this); } +}; + +/** A base class for listeners that want to know about changes to an ARAPlaybackRegion object. + + Use ARAPlaybackRegion::addListener() to register your listener with an ARAPlaybackRegion. + + @tags{ARA} +*/ +class JUCE_API ARAPlaybackRegionListener +{ +public: + /** Destructor. */ + virtual ~ARAPlaybackRegionListener() = default; + + ARA_DISABLE_UNREFERENCED_PARAMETER_WARNING_BEGIN + + /** Called before the playback region's properties are updated. + @param playbackRegion The playback region whose properties will be updated. + @param newProperties The playback region properties that will be assigned to \p playbackRegion. + */ + virtual void willUpdatePlaybackRegionProperties (ARAPlaybackRegion* playbackRegion, + ARA::PlugIn::PropertiesPtr newProperties) + { + ignoreUnused (playbackRegion, newProperties); + } + + /** Called after the playback region's properties are updated. + @param playbackRegion The playback region whose properties were updated. + */ + virtual void didUpdatePlaybackRegionProperties (ARAPlaybackRegion* playbackRegion) + { + ignoreUnused (playbackRegion); + } + + /** Called when the playback region's content (i.e. samples or notes) changes. + @param playbackRegion The playback region with updated content. + @param scopeFlags The scope of the content update. + */ + virtual void didUpdatePlaybackRegionContent (ARAPlaybackRegion* playbackRegion, + ARAContentUpdateScopes scopeFlags) + { + ignoreUnused (playbackRegion, scopeFlags); + } + + /** Called before the playback region is destroyed. + @param playbackRegion The playback region that will be destoyed. + */ + virtual void willDestroyPlaybackRegion (ARAPlaybackRegion* playbackRegion) + { + ignoreUnused (playbackRegion); + } + + ARA_DISABLE_UNREFERENCED_PARAMETER_WARNING_END +}; + +//============================================================================== +/** Base class representing an ARA playback region. + + @tags{ARA} +*/ +class JUCE_API ARAPlaybackRegion : public ARA::PlugIn::PlaybackRegion, + public ARAListenableModelClass, + public ARAObject +{ +public: + using PropertiesPtr = ARA::PlugIn::PropertiesPtr; + using Listener = ARAPlaybackRegionListener; + + using ARA::PlugIn::PlaybackRegion::PlaybackRegion; + + /** Returns the result of ARA::PlugIn::PlaybackRegion::getAudioModification() with the pointer cast + to ARAAudioModification*. + + If you have overridden ARADocumentControllerSpecialisation::doCreateAudioModification(), then you + can use the template parameter to cast the pointers to your subclass of ARAAudioModification. + */ + template + AudioModification_t* getAudioModification() const noexcept + { + return ARA::PlugIn::PlaybackRegion::getAudioModification(); + } + + /** Returns the result of ARA::PlugIn::PlaybackRegion::getRegionSequence() with the pointer cast + to ARARegionSequence*. + + If you have overridden ARADocumentControllerSpecialisation::doCreateRegionSequence(), then you + can use the template parameter to cast the pointers to your subclass of ARARegionSequence. + */ + template + RegionSequence_t* getRegionSequence() const noexcept + { + return ARA::PlugIn::PlaybackRegion::getRegionSequence(); + } + + size_t getNumChildren() const noexcept override { return 0; } + + ARAObject* getChild (size_t) override { return nullptr; } + + ARAObject* getParent() override; + + void visit (ARAObjectVisitor& visitor) override { visitor.visitPlaybackRegion (*this); } + + /** Used in getTimeRange to indicate whether the head and tail times should be included in the result. + */ + enum class IncludeHeadAndTail { no, yes }; + + /** Returns the playback time range of this playback region. + @param includeHeadAndTail Whether or not the range includes the head and tail + time of all playback regions in the sequence. + */ + Range getTimeRange (IncludeHeadAndTail includeHeadAndTail = IncludeHeadAndTail::no) const; + + /** Returns the playback sample range of this playback region. + @param sampleRate The rate at which the sample position should be calculated from + the time range. + @param includeHeadAndTail Whether or not the range includes the head and tail + time of all playback regions in the sequence. + */ + Range getSampleRange (double sampleRate, IncludeHeadAndTail includeHeadAndTail = IncludeHeadAndTail::no) const; + + /** Get the head length in seconds before the start of the region's playback time. */ + double getHeadTime() const; + + /** Get the tail length in seconds after the end of the region's playback time. */ + double getTailTime() const; + + /** Notify the ARA host and any listeners of a content update initiated by the plug-in. + This must be called by the plug-in model management code on the message thread whenever updating + the internal content representation, such as after the user edited the pitch of a note in the + underlying audio modification. + Listeners will be notified immediately. If \p notifyARAHost is true, a notification to the host + will be enqueued and sent out the next time it polls for updates. + + @param scopeFlags The scope of the content update. + @param notifyARAHost If true, the ARA host will be notified of the content change. + */ + void notifyContentChanged (ARAContentUpdateScopes scopeFlags, bool notifyARAHost); +}; + +/** A base class for listeners that want to know about changes to an ARARegionSequence object. + + Use ARARegionSequence::addListener() to register your listener with an ARARegionSequence. + + @tags{ARA} +*/ +class JUCE_API ARARegionSequenceListener +{ +public: + /** Destructor. */ + virtual ~ARARegionSequenceListener() = default; + + ARA_DISABLE_UNREFERENCED_PARAMETER_WARNING_BEGIN + + /** Called before the region sequence's properties are updated. + @param regionSequence The region sequence whose properties will be updated. + @param newProperties The region sequence properties that will be assigned to \p regionSequence. + */ + virtual void willUpdateRegionSequenceProperties (ARARegionSequence* regionSequence, + ARA::PlugIn::PropertiesPtr newProperties) + { + ignoreUnused (regionSequence, newProperties); + } + + /** Called after the region sequence's properties are updated. + @param regionSequence The region sequence whose properties were updated. + */ + virtual void didUpdateRegionSequenceProperties (ARARegionSequence* regionSequence) + { + ignoreUnused (regionSequence); + } + + /** Called before a playback region is removed from the region sequence. + @param regionSequence The region sequence that \p playbackRegion will be removed from. + @param playbackRegion The playback region that will be removed from \p regionSequence. + */ + virtual void willRemovePlaybackRegionFromRegionSequence (ARARegionSequence* regionSequence, + ARAPlaybackRegion* playbackRegion) + { + ignoreUnused (regionSequence, playbackRegion); + } + + /** Called after a playback region is added to the region sequence. + @param regionSequence The region sequence that \p playbackRegion was added to. + @param playbackRegion The playback region that was added to \p regionSequence. + */ + virtual void didAddPlaybackRegionToRegionSequence (ARARegionSequence* regionSequence, + ARAPlaybackRegion* playbackRegion) + { + ignoreUnused (regionSequence, playbackRegion); + } + + /** Called before the region sequence is destroyed. + @param regionSequence The region sequence that will be destoyed. + */ + virtual void willDestroyRegionSequence (ARARegionSequence* regionSequence) + { + ignoreUnused (regionSequence); + } + + ARA_DISABLE_UNREFERENCED_PARAMETER_WARNING_END +}; + +//============================================================================== +/** Base class representing an ARA region sequence. + + @tags{ARA} +*/ +class JUCE_API ARARegionSequence : public ARA::PlugIn::RegionSequence, + public ARAListenableModelClass, + public ARAObject +{ +public: + using PropertiesPtr = ARA::PlugIn::PropertiesPtr; + using Listener = ARARegionSequenceListener; + + using ARA::PlugIn::RegionSequence::RegionSequence; + + /** Returns the result of ARA::PlugIn::RegionSequence::getDocument() with the pointer cast + to ARADocument*. + + If you have overridden ARADocumentControllerSpecialisation::doCreateDocument(), then you + can use the template parameter to cast the pointers to your subclass of ARADocument. + */ + template + Document_t* getDocument() const noexcept + { + return ARA::PlugIn::RegionSequence::getDocument(); + } + + /** Returns the result of ARA::PlugIn::RegionSequence::getMusicalContext() with the pointer cast + to ARAMusicalContext*. + + If you have overridden ARADocumentControllerSpecialisation::doCreateMusicalContext(), then you + can use the template parameter to cast the pointers to your subclass of ARAMusicalContext. + */ + template + MusicalContext_t* getMusicalContext() const noexcept + { + return ARA::PlugIn::RegionSequence::getMusicalContext(); + } + + /** Returns the result of ARA::PlugIn::RegionSequence::getPlaybackRegions() with the pointers within cast + to ARAPlaybackRegion*. + + If you have overridden ARADocumentControllerSpecialisation::doCreatePlaybackRegion(), then you + can use the template parameter to cast the pointers to your subclass of ARAPlaybackRegion. + */ + template + const std::vector& getPlaybackRegions() const noexcept + { + return ARA::PlugIn::RegionSequence::getPlaybackRegions(); + } + + size_t getNumChildren() const noexcept override; + + ARAObject* getChild (size_t index) override; + + ARAObject* getParent () override { return getDocument(); } + + void visit (ARAObjectVisitor& visitor) override { visitor.visitRegionSequence (*this); } + + /** Returns the playback time range covered by the regions in this sequence. + @param includeHeadAndTail Whether or not the range includes the playback region's head and tail time. + */ + Range getTimeRange (ARAPlaybackRegion::IncludeHeadAndTail includeHeadAndTail = ARAPlaybackRegion::IncludeHeadAndTail::no) const; + + /** If all audio sources used by the playback regions in this region sequence have the same + sample rate, this rate is returned here, otherwise 0.0 is returned. + + If the region sequence has no playback regions, this also returns 0.0. + */ + double getCommonSampleRate() const; +}; + +/** A base class for listeners that want to know about changes to an ARAAudioSource object. + + Use ARAAudioSource::addListener() to register your listener with an ARAAudioSource. + + @tags{ARA} +*/ +class JUCE_API ARAAudioSourceListener +{ +public: + /** Destructor. */ + virtual ~ARAAudioSourceListener() = default; + + ARA_DISABLE_UNREFERENCED_PARAMETER_WARNING_BEGIN + + /** Called before the audio source's properties are updated. + @param audioSource The audio source whose properties will be updated. + @param newProperties The audio source properties that will be assigned to \p audioSource. + */ + virtual void willUpdateAudioSourceProperties (ARAAudioSource* audioSource, + ARA::PlugIn::PropertiesPtr newProperties) + { + ignoreUnused (audioSource, newProperties); + } + + /** Called after the audio source's properties are updated. + @param audioSource The audio source whose properties were updated. + */ + virtual void didUpdateAudioSourceProperties (ARAAudioSource* audioSource) + { + ignoreUnused (audioSource); + } + + /** Called when the audio source's content (i.e. samples or notes) changes. + @param audioSource The audio source with updated content. + @param scopeFlags The scope of the content update. + */ + virtual void doUpdateAudioSourceContent (ARAAudioSource* audioSource, ARAContentUpdateScopes scopeFlags) + { + ignoreUnused (audioSource, scopeFlags); + } + + /** Called to notify progress when an audio source is being analyzed. + @param audioSource The audio source being analyzed. + @param state Indicates start, intermediate update or completion of the analysis. + @param progress Progress normalized to the 0..1 range. + */ + virtual void didUpdateAudioSourceAnalysisProgress (ARAAudioSource* audioSource, + ARA::ARAAnalysisProgressState state, + float progress) + { + ignoreUnused (audioSource, state, progress); + } + + /** Called before access to an audio source's samples is enabled or disabled. + @param audioSource The audio source whose sample access state will be changed. + @param enable A bool indicating whether or not sample access will be enabled or disabled. + */ + virtual void willEnableAudioSourceSamplesAccess (ARAAudioSource* audioSource, bool enable) + { + ignoreUnused (audioSource, enable); + } + + /** Called after access to an audio source's samples is enabled or disabled. + @param audioSource The audio source whose sample access state was changed. + @param enable A bool indicating whether or not sample access was enabled or disabled. + */ + virtual void didEnableAudioSourceSamplesAccess (ARAAudioSource* audioSource, bool enable) + { + ignoreUnused (audioSource, enable); + } + + /** Called before an audio source is activated or deactivated when being removed / added from the host's undo history. + @param audioSource The audio source that will be activated or deactivated + @param deactivate A bool indicating whether \p audioSource was deactivated or activated. + */ + virtual void willDeactivateAudioSourceForUndoHistory (ARAAudioSource* audioSource, bool deactivate) + { + ignoreUnused (audioSource, deactivate); + } + + /** Called after an audio source is activated or deactivated when being removed / added from the host's undo history. + @param audioSource The audio source that was activated or deactivated + @param deactivate A bool indicating whether \p audioSource was deactivated or activated. + */ + virtual void didDeactivateAudioSourceForUndoHistory (ARAAudioSource* audioSource, bool deactivate) + { + ignoreUnused (audioSource, deactivate); + } + + /** Called after an audio modification is added to the audio source. + @param audioSource The region sequence that \p audioModification was added to. + @param audioModification The playback region that was added to \p audioSource. + */ + virtual void didAddAudioModificationToAudioSource (ARAAudioSource* audioSource, + ARAAudioModification* audioModification) + { + ignoreUnused (audioSource, audioModification); + } + + /** Called before an audio modification is removed from the audio source. + @param audioSource The audio source that \p audioModification will be removed from. + @param audioModification The audio modification that will be removed from \p audioSource. + */ + virtual void willRemoveAudioModificationFromAudioSource (ARAAudioSource* audioSource, + ARAAudioModification* audioModification) + { + ignoreUnused (audioSource, audioModification); + } + + /** Called before the audio source is destroyed. + @param audioSource The audio source that will be destoyed. + */ + virtual void willDestroyAudioSource (ARAAudioSource* audioSource) + { + ignoreUnused (audioSource); + } + + ARA_DISABLE_UNREFERENCED_PARAMETER_WARNING_END +}; + +//============================================================================== +/** Base class representing an ARA audio source. + + @tags{ARA} +*/ +class JUCE_API ARAAudioSource : public ARA::PlugIn::AudioSource, + public ARAListenableModelClass, + public ARAObject +{ +public: + using PropertiesPtr = ARA::PlugIn::PropertiesPtr; + using ARAAnalysisProgressState = ARA::ARAAnalysisProgressState; + using Listener = ARAAudioSourceListener; + + using ARA::PlugIn::AudioSource::AudioSource; + + /** Returns the result of ARA::PlugIn::AudioSource::getDocument() with the pointer cast + to ARADocument*. + + If you have overridden ARADocumentControllerSpecialisation::doCreateDocument(), then you + can use the template parameter to cast the pointers to your subclass of ARADocument. + */ + template + Document_t* getDocument() const noexcept + { + return ARA::PlugIn::AudioSource::getDocument(); + } + + /** Returns the result of ARA::PlugIn::AudioSource::getAudioModifications() with the pointers + within cast to ARAAudioModification*. + + If you have overridden ARADocumentControllerSpecialisation::doCreateAudioModification(), + then you can use the template parameter to cast the pointers to your subclass of + ARAAudioModification. + */ + template + const std::vector& getAudioModifications() const noexcept + { + return ARA::PlugIn::AudioSource::getAudioModifications(); + } + + size_t getNumChildren() const noexcept override; + + ARAObject* getChild (size_t index) override; + + ARAObject* getParent() override { return getDocument(); } + + void visit (ARAObjectVisitor& visitor) override { visitor.visitAudioSource (*this); } + + /** Notify the ARA host and any listeners of analysis progress. + Contrary to most ARA functions, this call can be made from any thread. + The implementation will enqueue these notifications and later post them from the message thread. + Calling code must ensure start and completion state are always balanced, + and must send updates in ascending order. + */ + void notifyAnalysisProgressStarted(); + + /** \copydoc notifyAnalysisProgressStarted + @param progress Progress normalized to the 0..1 range. + */ + void notifyAnalysisProgressUpdated (float progress); + + /** \copydoc notifyAnalysisProgressStarted + */ + void notifyAnalysisProgressCompleted(); + + /** Notify the ARA host and any listeners of a content update initiated by the plug-in. + This must be called by the plug-in model management code on the message thread whenever updating + the internal content representation, such as after successfully analyzing a new tempo map. + Listeners will be notified immediately. If \p notifyARAHost is true, a notification to the host + will be enqueued and sent out the next time it polls for updates. + \p notifyARAHost must be false if the update was triggered by the host via doUpdateAudioSourceContent(). + Furthermore, \p notifyARAHost must be false if the updated content is being restored from an archive. + + @param scopeFlags The scope of the content update. + @param notifyARAHost If true, the ARA host will be notified of the content change. + */ + void notifyContentChanged (ARAContentUpdateScopes scopeFlags, bool notifyARAHost); + +public: + friend ARADocumentController; + ARA::PlugIn::AnalysisProgressTracker internalAnalysisProgressTracker; +}; + +/** A base class for listeners that want to know about changes to an ARAAudioModification object. + + Use ARAAudioModification::addListener() to register your listener with an ARAAudioModification. + + @tags{ARA} +*/ +class JUCE_API ARAAudioModificationListener +{ +public: + /** Destructor. */ + virtual ~ARAAudioModificationListener() = default; + + ARA_DISABLE_UNREFERENCED_PARAMETER_WARNING_BEGIN + + /** Called before the audio modification's properties are updated. + @param audioModification The audio modification whose properties will be updated. + @param newProperties The audio modification properties that will be assigned to \p audioModification. + */ + virtual void willUpdateAudioModificationProperties (ARAAudioModification* audioModification, + ARA::PlugIn::PropertiesPtr newProperties) + { + ignoreUnused (audioModification, newProperties); + } + + /** Called after the audio modification's properties are updated. + @param audioModification The audio modification whose properties were updated. + */ + virtual void didUpdateAudioModificationProperties (ARAAudioModification* audioModification) + { + ignoreUnused (audioModification); + } + + /** Called when the audio modification's content (i.e. samples or notes) changes. + @param audioModification The audio modification with updated content. + @param scopeFlags The scope of the content update. + */ + virtual void didUpdateAudioModificationContent (ARAAudioModification* audioModification, ARAContentUpdateScopes scopeFlags) + { + ignoreUnused (audioModification, scopeFlags); + } + + /** Called before an audio modification is activated or deactivated when being removed / added from the host's undo history. + @param audioModification The audio modification that was activated or deactivated + @param deactivate A bool indicating whether \p audioModification was deactivated or activated. + */ + virtual void willDeactivateAudioModificationForUndoHistory (ARAAudioModification* audioModification, bool deactivate) + { + ignoreUnused (audioModification, deactivate); + } + + /** Called after an audio modification is activated or deactivated when being removed / added from the host's undo history. + @param audioModification The audio modification that was activated or deactivated + @param deactivate A bool indicating whether \p audioModification was deactivated or activated. + */ + virtual void didDeactivateAudioModificationForUndoHistory (ARAAudioModification* audioModification, bool deactivate) + { + ignoreUnused (audioModification, deactivate); + } + + /** Called after a playback region is added to the audio modification. + @param audioModification The audio modification that \p playbackRegion was added to. + @param playbackRegion The playback region that was added to \p audioModification. + */ + virtual void didAddPlaybackRegionToAudioModification (ARAAudioModification* audioModification, + ARAPlaybackRegion* playbackRegion) + { + ignoreUnused (audioModification, playbackRegion); + } + + /** Called before a playback region is removed from the audio modification. + @param audioModification The audio modification that \p playbackRegion will be removed from. + @param playbackRegion The playback region that will be removed from \p audioModification. + */ + virtual void willRemovePlaybackRegionFromAudioModification (ARAAudioModification* audioModification, + ARAPlaybackRegion* playbackRegion) + { + ignoreUnused (audioModification, playbackRegion); + } + + /** Called before the audio modification is destroyed. + @param audioModification The audio modification that will be destoyed. + */ + virtual void willDestroyAudioModification (ARAAudioModification* audioModification) + { + ignoreUnused (audioModification); + } + + ARA_DISABLE_UNREFERENCED_PARAMETER_WARNING_END +}; + +//============================================================================== +/** Base class representing an ARA audio modification. + + @tags{ARA} +*/ +class JUCE_API ARAAudioModification : public ARA::PlugIn::AudioModification, + public ARAListenableModelClass, + public ARAObject +{ +public: + using PropertiesPtr = ARA::PlugIn::PropertiesPtr; + using Listener = ARAAudioModificationListener; + + using ARA::PlugIn::AudioModification::AudioModification; + + /** Returns the result of ARA::PlugIn::AudioModification::getAudioSource() with the pointer cast + to ARAAudioSource*. + + If you have overridden ARADocumentControllerSpecialisation::doCreateAudioSource(), then you + can use the template parameter to cast the pointers to your subclass of ARAAudioSource. + */ + template + AudioSource_t* getAudioSource() const noexcept + { + return ARA::PlugIn::AudioModification::getAudioSource(); + } + + /** Returns the result of ARA::PlugIn::AudioModification::getPlaybackRegions() with the + pointers within cast to ARAPlaybackRegion*. + + If you have overridden ARADocumentControllerSpecialisation::doCreatePlaybackRegion(), then + you can use the template parameter to cast the pointers to your subclass of ARAPlaybackRegion. + */ + template + const std::vector& getPlaybackRegions() const noexcept + { + return ARA::PlugIn::AudioModification::getPlaybackRegions(); + } + + size_t getNumChildren() const noexcept override; + + ARAObject* getChild (size_t index) override; + + ARAObject* getParent() override { return getAudioSource(); } + + void visit (ARAObjectVisitor& visitor) override { visitor.visitAudioModification (*this); } + + /** Notify the ARA host and any listeners of a content update initiated by the plug-in. + This must be called by the plug-in model management code on the message thread whenever updating + the internal content representation, such as after the user editing the pitch of a note. + Listeners will be notified immediately. If \p notifyARAHost is true, a notification to the host + will be enqueued and sent out the next time it polls for updates. + \p notifyARAHost must be false if the updated content is being restored from an archive. + + @param scopeFlags The scope of the content update. + @param notifyARAHost If true, the ARA host will be notified of the content change. + */ + void notifyContentChanged (ARAContentUpdateScopes scopeFlags, bool notifyARAHost); +}; + +} // namespace juce diff --git a/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.cpp b/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.cpp new file mode 100644 index 0000000000..faad8b1903 --- /dev/null +++ b/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.cpp @@ -0,0 +1,85 @@ +/* + ============================================================================== + + This file is part of the JUCE 7 technical preview. + Copyright (c) 2022 - Raw Material Software Limited + + You may use this code under the terms of the GPL v3 + (see www.gnu.org/licenses). + + For the technical preview this file cannot be licensed commercially. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#include "juce_ARAPlugInInstanceRoles.h" + +namespace juce +{ + +bool ARARenderer::processBlock (AudioBuffer& buffer, + AudioProcessor::Realtime realtime, + const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept +{ + ignoreUnused (buffer, realtime, positionInfo); + + // If you hit this assertion then either the caller called the double + // precision version of processBlock on a processor which does not support it + // (i.e. supportsDoublePrecisionProcessing() returns false), or the implementation + // of the ARARenderer forgot to override the double precision version of this method + jassertfalse; + + return false; +} + +//============================================================================== +#if ARA_VALIDATE_API_CALLS +void ARAPlaybackRenderer::addPlaybackRegion (ARA::ARAPlaybackRegionRef playbackRegionRef) noexcept +{ + if (araExtension) + ARA_VALIDATE_API_STATE (! araExtension->isPrepared); + + ARA::PlugIn::PlaybackRenderer::addPlaybackRegion (playbackRegionRef); +} + +void ARAPlaybackRenderer::removePlaybackRegion (ARA::ARAPlaybackRegionRef playbackRegionRef) noexcept +{ + if (araExtension) + ARA_VALIDATE_API_STATE (! araExtension->isPrepared); + + ARA::PlugIn::PlaybackRenderer::removePlaybackRegion (playbackRegionRef); +} +#endif + +//============================================================================== +void ARAEditorView::doNotifySelection (const ARA::PlugIn::ViewSelection* viewSelection) noexcept +{ + listeners.call ([&] (Listener& l) + { + l.onNewSelection (*viewSelection); + }); +} + +void ARAEditorView::doNotifyHideRegionSequences (std::vector const& regionSequences) noexcept +{ + listeners.call ([&] (Listener& l) + { + l.onHideRegionSequences (ARA::vector_cast (regionSequences)); + }); +} + +void ARAEditorView::addListener (Listener* l) +{ + listeners.add (l); +} + +void ARAEditorView::removeListener (Listener* l) +{ + listeners.remove (l); +} + +} // namespace juce diff --git a/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.h b/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.h new file mode 100644 index 0000000000..071c9573bc --- /dev/null +++ b/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.h @@ -0,0 +1,246 @@ +#pragma once + +namespace juce +{ + +//============================================================================== +/** Base class for a renderer fulfilling either the ARAPlaybackRenderer or the ARAEditorRenderer role. + + Instances of either subclass are constructed by the DocumentController. + + @tags{ARA} +*/ +class JUCE_API ARARenderer +{ +public: + enum class AlwaysNonRealtime { no, yes }; + + virtual ~ARARenderer() = default; + + /** Initialises the renderer for playback. + + @param sampleRate The sample rate that will be used for the data that is sent + to the renderer + @param maximumSamplesPerBlock The maximum number of samples that will be in the blocks + sent to process() method + @param numChannels The number of channels that the process() method will be + expected to handle + @param precision This should be the same as the result of getProcessingPrecision() + for the enclosing AudioProcessor + @param alwaysNonRealtime yes if this renderer is never used in realtime (e.g. if + providing data for views only) + */ + virtual void prepareToPlay (double sampleRate, + int maximumSamplesPerBlock, + int numChannels, + AudioProcessor::ProcessingPrecision precision, + AlwaysNonRealtime alwaysNonRealtime = AlwaysNonRealtime::no) + { + ignoreUnused (sampleRate, maximumSamplesPerBlock, numChannels, precision, alwaysNonRealtime); + } + + /** Frees render resources allocated in prepareToPlay(). */ + virtual void releaseResources() {} + + /** Resets the internal state variables of the renderer. */ + virtual void reset() {} + + /** Renders the output into the given buffer. Returns true if rendering executed without error, + false otherwise. + + @param buffer The output buffer for the rendering. ARAPlaybackRenderers will + replace the sample data, while ARAEditorRenderer will add to it. + @param realtime Indicates whether the call is executed under real time constraints. + The value of this parameter may change from one call to the next, + and if the value is yes, the rendering may fail if the required + samples cannot be obtained in time. + @param positionInfo Current song position, playback state and playback loop location. + There should be no need to access the bpm, timeSig and ppqPosition + members in any ARA renderer since ARA provides that information with + random access in its model graph. + + Returns false if non-ARA fallback rendering is required and true otherwise. + */ + virtual bool processBlock (AudioBuffer& buffer, + AudioProcessor::Realtime realtime, + const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept = 0; + + /** Renders the output into the given buffer. Returns true if rendering executed without error, + false otherwise. + + @param buffer The output buffer for the rendering. ARAPlaybackRenderers will + replace the sample data, while ARAEditorRenderer will add to it. + @param realtime Indicates whether the call is executed under real time constraints. + The value of this parameter may change from one call to the next, + and if the value is yes, the rendering may fail if the required + samples cannot be obtained in time. + @param positionInfo Current song position, playback state and playback loop location. + There should be no need to access the bpm, timeSig and ppqPosition + members in any ARA renderer since ARA provides that information with + random access in its model graph. + + Returns false if non-ARA fallback rendering is required and true otherwise. + */ + virtual bool processBlock (AudioBuffer& buffer, + AudioProcessor::Realtime realtime, + const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept; +}; + +//============================================================================== +/** Base class for a renderer fulfilling the ARAPlaybackRenderer role as described in the ARA SDK. + + Instances of this class are constructed by the DocumentController. If you are subclassing + ARAPlaybackRenderer, make sure to call the base class implementation of any overridden function, + except for processBlock. + + @tags{ARA} +*/ +class JUCE_API ARAPlaybackRenderer : public ARA::PlugIn::PlaybackRenderer, + public ARARenderer +{ +public: + using ARA::PlugIn::PlaybackRenderer::PlaybackRenderer; + + bool processBlock (AudioBuffer& buffer, + AudioProcessor::Realtime realtime, + const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept override + { + ignoreUnused (buffer, realtime, positionInfo); + return false; + } + + // Shadowing templated getters to default to JUCE versions of the returned classes + /** Returns the PlaybackRegions + * + * @tparam PlaybackRegion_t + * @return + */ + template + std::vector const& getPlaybackRegions() const noexcept + { + return ARA::PlugIn::PlaybackRenderer::getPlaybackRegions(); + } + +#if ARA_VALIDATE_API_CALLS + void addPlaybackRegion (ARA::ARAPlaybackRegionRef playbackRegionRef) noexcept override; + void removePlaybackRegion (ARA::ARAPlaybackRegionRef playbackRegionRef) noexcept override; + AudioProcessorARAExtension* araExtension {}; +#endif + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARAPlaybackRenderer) +}; + +//============================================================================== +/** Base class for a renderer fulfilling the ARAEditorRenderer role as described in the ARA SDK. + + Instances of this class are constructed by the DocumentController. If you are subclassing + ARAEditorRenderer, make sure to call the base class implementation of any overridden function, + except for processBlock. + + @tags{ARA} +*/ +class JUCE_API ARAEditorRenderer : public ARA::PlugIn::EditorRenderer, + public ARARenderer +{ +public: + using ARA::PlugIn::EditorRenderer::EditorRenderer; + + // Shadowing templated getters to default to JUCE versions of the returned classes + template + std::vector const& getPlaybackRegions() const noexcept + { + return ARA::PlugIn::EditorRenderer::getPlaybackRegions(); + } + + template + std::vector const& getRegionSequences() const noexcept + { + return ARA::PlugIn::EditorRenderer::getRegionSequences(); + } + + // By default, editor renderers will just let the signal pass through unaltered. + // If you're overriding this to implement actual audio preview, remember to check + // isNonRealtime of the process context - typically preview is limited to realtime. + bool processBlock (AudioBuffer& buffer, + AudioProcessor::Realtime isNonRealtime, + const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept override + { + ignoreUnused (buffer, isNonRealtime, positionInfo); + return true; + } + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARAEditorRenderer) +}; + +//============================================================================== +/** Base class for a renderer fulfilling the ARAEditorView role as described in the ARA SDK. + + Instances of this class are constructed by the DocumentController. If you are subclassing + ARAEditorView, make sure to call the base class implementation of overridden functions. + + @tags{ARA} +*/ +class JUCE_API ARAEditorView : public ARA::PlugIn::EditorView +{ +public: + using ARA::PlugIn::EditorView::EditorView; + + // Shadowing templated getters to default to JUCE versions of the returned classes + template + std::vector const& getHiddenRegionSequences() const noexcept + { + return ARA::PlugIn::EditorView::getHiddenRegionSequences(); + } + + // Base class implementation must be called if overridden + void doNotifySelection (const ARA::PlugIn::ViewSelection* currentSelection) noexcept override; + + // Base class implementation must be called if overridden + void doNotifyHideRegionSequences (std::vector const& regionSequences) noexcept override; + + /** A base class for listeners that want to know about changes to an ARAEditorView object. + + Use ARAEditorView::addListener() to register your listener with an ARAEditorView. + */ + class JUCE_API Listener + { + public: + /** Destructor. */ + virtual ~Listener() = default; + + ARA_DISABLE_UNREFERENCED_PARAMETER_WARNING_BEGIN + + /** Called when the editor view's selection changes. + @param viewSelection The current selection state + */ + virtual void onNewSelection (const ARA::PlugIn::ViewSelection& viewSelection) + { + ignoreUnused (viewSelection); + } + + /** Called when region sequences are flagged as hidden in the host UI. + @param regionSequences A vector containing all hidden region sequences. + */ + virtual void onHideRegionSequences (std::vector const& regionSequences) + { + ignoreUnused (regionSequences); + } + + ARA_DISABLE_UNREFERENCED_PARAMETER_WARNING_END + }; + + /** \copydoc ARAListenableModelClass::addListener */ + void addListener (Listener* l); + + /** \copydoc ARAListenableModelClass::removeListener */ + void removeListener (Listener* l); + +private: + ListenerList listeners; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARAEditorView) +}; + +} diff --git a/modules/juce_audio_processors/utilities/ARA/juce_ARA_utils.cpp b/modules/juce_audio_processors/utilities/ARA/juce_ARA_utils.cpp new file mode 100644 index 0000000000..01869a4073 --- /dev/null +++ b/modules/juce_audio_processors/utilities/ARA/juce_ARA_utils.cpp @@ -0,0 +1,25 @@ +/* + ============================================================================== + + This file is part of the JUCE 7 technical preview. + Copyright (c) 2022 - Raw Material Software Limited + + You may use this code under the terms of the GPL v3 + (see www.gnu.org/licenses). + + For the technical preview this file cannot be licensed commercially. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#if JucePlugin_Enable_ARA +#include "juce_ARADocumentControllerCommon.cpp" +#include "juce_ARADocumentController.cpp" +#include "juce_ARAModelObjects.cpp" +#include "juce_ARAPlugInInstanceRoles.cpp" +#include "juce_AudioProcessor_ARAExtensions.cpp" +#endif diff --git a/modules/juce_audio_processors/utilities/ARA/juce_ARA_utils.h b/modules/juce_audio_processors/utilities/ARA/juce_ARA_utils.h new file mode 100644 index 0000000000..200a0787d6 --- /dev/null +++ b/modules/juce_audio_processors/utilities/ARA/juce_ARA_utils.h @@ -0,0 +1,78 @@ +/* + ============================================================================== + + This file is part of the JUCE 7 technical preview. + Copyright (c) 2022 - Raw Material Software Limited + + You may use this code under the terms of the GPL v3 + (see www.gnu.org/licenses). + + For the technical preview this file cannot be licensed commercially. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#if JucePlugin_Enable_ARA + +// Include ARA SDK headers +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wgnu-zero-variadic-macro-arguments", + "-Wunused-parameter") +JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6387) + +#include + +JUCE_END_IGNORE_WARNINGS_GCC_LIKE +JUCE_END_IGNORE_WARNINGS_MSVC + +namespace juce +{ + +using ARAViewSelection = ARA::PlugIn::ViewSelection; +using ARAContentUpdateScopes = ARA::ContentUpdateScopes; +using ARARestoreObjectsFilter = ARA::PlugIn::RestoreObjectsFilter; +using ARAStoreObjectsFilter = ARA::PlugIn::StoreObjectsFilter; + +/** Converts an ARA::ARAUtf8String to a JUCE String. */ +inline String convertARAString (ARA::ARAUtf8String str) +{ + return String (CharPointer_UTF8 (str)); +} + +/** Converts a potentially NULL ARA::ARAUtf8String to a JUCE String. + + Returns the JUCE equivalent of the provided string if it's not nullptr, and the fallback string + otherwise. +*/ +inline String convertOptionalARAString (ARA::ARAUtf8String str, const String& fallbackString = String()) +{ + return (str != nullptr) ? convertARAString (str) : fallbackString; +} + +/** Converts an ARA::ARAColor* to a JUCE Colour. */ +inline Colour convertARAColour (const ARA::ARAColor* colour) +{ + return Colour::fromFloatRGBA (colour->r, colour->g, colour->b, 1.0f); +} + +/** Converts a potentially NULL ARA::ARAColor* to a JUCE Colour. + + Returns the JUCE equivalent of the provided colour if it's not nullptr, and the fallback colour + otherwise. +*/ +inline Colour convertOptionalARAColour (const ARA::ARAColor* colour, const Colour& fallbackColour = Colour()) +{ + return (colour != nullptr) ? convertARAColour (colour) : fallbackColour; +} + +} // namespace juce + +#include "juce_ARAModelObjects.h" +#include "juce_ARADocumentController.h" +#include "juce_AudioProcessor_ARAExtensions.h" +#include "juce_ARAPlugInInstanceRoles.h" + +#endif diff --git a/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.cpp b/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.cpp new file mode 100644 index 0000000000..83102f09ab --- /dev/null +++ b/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.cpp @@ -0,0 +1,149 @@ +/* + ============================================================================== + + This file is part of the JUCE 7 technical preview. + Copyright (c) 2022 - Raw Material Software Limited + + You may use this code under the terms of the GPL v3 + (see www.gnu.org/licenses). + + For the technical preview this file cannot be licensed commercially. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#include "juce_AudioProcessor_ARAExtensions.h" + +namespace juce +{ + +//============================================================================== +bool AudioProcessorARAExtension::getTailLengthSecondsForARA (double& tailLength) const +{ + if (! isBoundToARA()) + return false; + + tailLength = 0.0; + + if (auto playbackRenderer = getPlaybackRenderer()) + for (const auto& playbackRegion : playbackRenderer->getPlaybackRegions()) + tailLength = jmax (tailLength, playbackRegion->getTailTime()); + + return true; +} + +bool AudioProcessorARAExtension::prepareToPlayForARA (double sampleRate, + int samplesPerBlock, + int numChannels, + AudioProcessor::ProcessingPrecision precision) +{ +#if ARA_VALIDATE_API_CALLS + isPrepared = true; +#endif + + if (! isBoundToARA()) + return false; + + if (auto playbackRenderer = getPlaybackRenderer()) + playbackRenderer->prepareToPlay (sampleRate, samplesPerBlock, numChannels, precision); + + if (auto editorRenderer = getEditorRenderer()) + editorRenderer->prepareToPlay (sampleRate, samplesPerBlock, numChannels, precision); + + return true; +} + +bool AudioProcessorARAExtension::releaseResourcesForARA() +{ +#if ARA_VALIDATE_API_CALLS + isPrepared = false; +#endif + + if (! isBoundToARA()) + return false; + + if (auto playbackRenderer = getPlaybackRenderer()) + playbackRenderer->releaseResources(); + + if (auto editorRenderer = getEditorRenderer()) + editorRenderer->releaseResources(); + + return true; +} + +bool AudioProcessorARAExtension::processBlockForARA (AudioBuffer& buffer, + AudioProcessor::Realtime realtime, + const AudioPlayHead::CurrentPositionInfo& positionInfo) +{ + // validate that the host has prepared us before processing + ARA_VALIDATE_API_STATE (isPrepared); + + if (! isBoundToARA()) + return false; + + // Render our ARA playback regions for this buffer. + if (auto playbackRenderer = getPlaybackRenderer()) + playbackRenderer->processBlock (buffer, realtime, positionInfo); + + // Render our ARA editor regions and sequences for this buffer. + // This example does not support editor rendering and thus uses the default implementation, + // which is a no-op and could be omitted in actual plug-ins to optimize performance. + if (auto editorRenderer = getEditorRenderer()) + editorRenderer->processBlock (buffer, realtime, positionInfo); + + return true; +} + +bool AudioProcessorARAExtension::processBlockForARA (AudioBuffer& buffer, + juce::AudioProcessor::Realtime realtime, + AudioPlayHead* playhead) +{ + AudioPlayHead::CurrentPositionInfo positionInfo; + + if (! isBoundToARA() || ! playhead || ! playhead->getCurrentPosition (positionInfo)) + positionInfo.resetToDefault(); + + return processBlockForARA (buffer, realtime, positionInfo); +} + +//============================================================================== +void AudioProcessorARAExtension::didBindToARA() noexcept +{ + // validate that the ARA binding is not established by the host while prepared to play +#if ARA_VALIDATE_API_CALLS + ARA_VALIDATE_API_STATE (! isPrepared); + if (auto playbackRenderer = getPlaybackRenderer()) + playbackRenderer->araExtension = this; +#endif + +#if (! JUCE_DISABLE_ASSERTIONS) + // validate proper subclassing of the instance role classes + if (auto playbackRenderer = getPlaybackRenderer()) + jassert (dynamic_cast (playbackRenderer) != nullptr); + if (auto editorRenderer = getEditorRenderer()) + jassert (dynamic_cast (editorRenderer) != nullptr); + if (auto editorView = getEditorView()) + jassert (dynamic_cast (editorView) != nullptr); +#endif +} + +//============================================================================== + +AudioProcessorEditorARAExtension::AudioProcessorEditorARAExtension (AudioProcessor* audioProcessor) + : araProcessorExtension (dynamic_cast (audioProcessor)) +{ + if (isARAEditorView()) + getARAEditorView()->setEditorOpen (true); +} + +AudioProcessorEditorARAExtension::~AudioProcessorEditorARAExtension() +{ + if (isARAEditorView()) + getARAEditorView()->setEditorOpen (false); +} + +} // namespace juce diff --git a/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.h b/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.h new file mode 100644 index 0000000000..27cc4090b6 --- /dev/null +++ b/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.h @@ -0,0 +1,179 @@ +#pragma once + +namespace juce +{ + +class AudioProcessor; +class ARAPlaybackRenderer; +class ARAEditorRenderer; +class ARAEditorView; + +//============================================================================== +/** Extension class meant to be subclassed by the plugin's implementation of @see AudioProcessor. + + Subclassing AudioProcessorARAExtension allows access to the three possible plugin instance + roles as defined by the ARA SDK. Hosts can assign any subset of roles to each plugin instance. + + @tags{ARA} +*/ +class JUCE_API AudioProcessorARAExtension : public ARA::PlugIn::PlugInExtension +{ +public: + AudioProcessorARAExtension() = default; + + //============================================================================== + /** Returns the result of ARA::PlugIn::PlugInExtension::getPlaybackRenderer() with the pointer + cast to ARAPlaybackRenderer*. + + If you have overridden ARADocumentControllerSpecialisation::doCreatePlaybackRenderer(), + then you can use the template parameter to cast the pointers to your subclass of + ARAPlaybackRenderer. + */ + template + PlaybackRenderer_t* getPlaybackRenderer() const noexcept + { + return ARA::PlugIn::PlugInExtension::getPlaybackRenderer(); + } + + /** Returns the result of ARA::PlugIn::PlugInExtension::getEditorRenderer() with the pointer + cast to ARAEditorRenderer*. + + If you have overridden ARADocumentControllerSpecialisation::doCreateEditorRenderer(), + then you can use the template parameter to cast the pointers to your subclass of + ARAEditorRenderer. + */ + template + EditorRenderer_t* getEditorRenderer() const noexcept + { + return ARA::PlugIn::PlugInExtension::getEditorRenderer(); + } + + /** Returns the result of ARA::PlugIn::PlugInExtension::getEditorView() with the pointer + cast to ARAEditorView*. + + If you have overridden ARADocumentControllerSpecialisation::doCreateEditorView(), + then you can use the template parameter to cast the pointers to your subclass of + ARAEditorView. + */ + template + EditorView_t* getEditorView() const noexcept + { + return ARA::PlugIn::PlugInExtension::getEditorView(); + } + + //============================================================================== + /** Returns true if plugin instance fulfills the ARAPlaybackRenderer role. */ + bool isPlaybackRenderer() const noexcept + { + return ARA::PlugIn::PlugInExtension::getPlaybackRenderer() != nullptr; + } + + /** Returns true if plugin instance fulfills the ARAEditorRenderer role. */ + bool isEditorRenderer() const noexcept + { + return ARA::PlugIn::PlugInExtension::getEditorRenderer() != nullptr; + } + + /** Returns true if plugin instance fulfills the ARAEditorView role. */ + bool isEditorView() const noexcept + { + return ARA::PlugIn::PlugInExtension::getEditorView() != nullptr; + } + + //============================================================================== +#if ARA_VALIDATE_API_CALLS + bool isPrepared { false }; +#endif + +protected: + /** Implementation helper for AudioProcessor::getTailLengthSeconds(). + + If bound to ARA, this traverses the instance roles to retrieve the respective tail time + and returns true. Otherwise returns false and leaves tailLength unmodified. + */ + bool getTailLengthSecondsForARA (double& tailLength) const; + + /** Implementation helper for AudioProcessor::prepareToPlay(). + + If bound to ARA, this traverses the instance roles to prepare them for play and returns + true. Otherwise returns false and does nothing. + */ + bool prepareToPlayForARA (double sampleRate, + int samplesPerBlock, + int numChannels, + AudioProcessor::ProcessingPrecision precision); + + /** Implementation helper for AudioProcessor::releaseResources(). + + If bound to ARA, this traverses the instance roles to let them release resources and returns + true. Otherwise returns false and does nothing. + */ + bool releaseResourcesForARA(); + + /** Implementation helper for AudioProcessor::processBlock(). + + If bound to ARA, this traverses the instance roles to let them process the block and returns + true. Otherwise returns false and does nothing. + + Use this overload if your rendering code already has a current positionInfo available. + */ + bool processBlockForARA (AudioBuffer& buffer, + AudioProcessor::Realtime realtime, + const AudioPlayHead::CurrentPositionInfo& positionInfo); + + /** Implementation helper for AudioProcessor::processBlock(). + + If bound to ARA, this traverses the instance roles to let them process the block and returns + true. Otherwise returns false and does nothing. + + Use this overload if your rendering code does not have a current positionInfo available. + */ + bool processBlockForARA (AudioBuffer& buffer, AudioProcessor::Realtime isNonRealtime, AudioPlayHead* playhead); + + //============================================================================== + /** Optional hook for derived classes to perform any additional initialization that may be needed. + + If overriding this, make sure you call the base class implementation from your override. + */ + void didBindToARA() noexcept override; + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorARAExtension) +}; + +//============================================================================== +/** Extension class meant to be subclassed by the plugin's implementation of @see AudioProcessorEditor. + + Subclassing AudioProcessorARAExtension allows access to the ARAEditorView instance role as + described by the ARA SDK. + + @tags{ARA} +*/ +class JUCE_API AudioProcessorEditorARAExtension +{ +public: + /** Constructor. */ + explicit AudioProcessorEditorARAExtension (AudioProcessor* audioProcessor); + + /** \copydoc AudioProcessorARAExtension::getEditorView */ + template + EditorView_t* getARAEditorView() const noexcept + { + return (this->araProcessorExtension != nullptr) ? this->araProcessorExtension->getEditorView() + : nullptr; + } + + /** \copydoc AudioProcessorARAExtension::isEditorView */ + bool isARAEditorView() const noexcept { return getARAEditorView() != nullptr; } + +protected: + /** Destructor. */ + ~AudioProcessorEditorARAExtension(); + +private: + AudioProcessorARAExtension* araProcessorExtension; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorEditorARAExtension) +}; + +} // namespace juce