/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2022 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 7 End-User License Agreement and JUCE Privacy Policy. End User License Agreement: www.juce.com/juce-7-licence Privacy Policy: www.juce.com/juce-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). 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* const* 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* const* 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::PositionInfo positionInfo; ReadWriteLock lock; static constexpr int maximumBlockSize = 4 * 1024; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARAPlaybackRegionReader) }; } // namespace juce