| 
							- /*
 -   ==============================================================================
 - 
 -    This file is part of the JUCE examples.
 -    Copyright (c) 2022 - Raw Material Software Limited
 - 
 -    The code included in this file is provided under the terms of the ISC license
 -    http://www.isc.org/downloads/software-support-policy/isc-license. Permission
 -    To use, copy, modify, and/or distribute this software for any purpose with or
 -    without fee is hereby granted provided that the above copyright notice and
 -    this permission notice appear in all copies.
 - 
 -    THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
 -    WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
 -    PURPOSE, ARE DISCLAIMED.
 - 
 -   ==============================================================================
 - */
 - 
 - /*******************************************************************************
 -  The block below describes the properties of this PIP. A PIP is a short snippet
 -  of code that can be read by the Projucer and used to generate a JUCE project.
 - 
 -  BEGIN_JUCE_PIP_METADATA
 - 
 -  name:                     ARAPluginDemo
 -  version:                  1.0.0
 -  vendor:                   JUCE
 -  website:                  http://juce.com
 -  description:              Audio plugin using the ARA API.
 - 
 -  dependencies:             juce_audio_basics, juce_audio_devices, juce_audio_formats,
 -                            juce_audio_plugin_client, juce_audio_processors,
 -                            juce_audio_utils, juce_core, juce_data_structures,
 -                            juce_events, juce_graphics, juce_gui_basics, juce_gui_extra
 -  exporters:                xcode_mac, vs2022
 - 
 -  moduleFlags:              JUCE_STRICT_REFCOUNTEDPOINTER=1
 - 
 -  type:                     AudioProcessor
 -  mainClass:                ARADemoPluginAudioProcessor
 -  documentControllerClass:  ARADemoPluginDocumentControllerSpecialisation
 - 
 -  useLocalCopy:             1
 - 
 -  END_JUCE_PIP_METADATA
 - 
 - *******************************************************************************/
 - 
 - #pragma once
 - 
 - 
 - //==============================================================================
 - struct PreviewState
 - {
 -     std::atomic<double> previewTime { 0.0 };
 -     std::atomic<ARAPlaybackRegion*> previewedRegion { nullptr };
 - };
 - 
 - class SharedTimeSliceThread  : public TimeSliceThread
 - {
 - public:
 -     SharedTimeSliceThread()
 -         : TimeSliceThread (String (JucePlugin_Name) + " ARA Sample Reading Thread")
 -     {
 -         startThread (7);  // Above default priority so playback is fluent, but below realtime
 -     }
 - };
 - 
 - class AsyncConfigurationCallback  : private AsyncUpdater
 - {
 - public:
 -     explicit AsyncConfigurationCallback (std::function<void()> callbackIn)
 -         : callback (std::move (callbackIn)) {}
 - 
 -     ~AsyncConfigurationCallback() override { cancelPendingUpdate(); }
 - 
 -     template <typename RequiresLock>
 -     auto withLock (RequiresLock&& fn)
 -     {
 -         const SpinLock::ScopedTryLockType scope (processingFlag);
 -         return fn (scope.isLocked());
 -     }
 - 
 -     void startConfigure() { triggerAsyncUpdate(); }
 - 
 - private:
 -     void handleAsyncUpdate() override
 -     {
 -         const SpinLock::ScopedLockType scope (processingFlag);
 -         callback();
 -     }
 - 
 -     std::function<void()> callback;
 -     SpinLock processingFlag;
 - };
 - 
 - class Looper
 - {
 - public:
 -     Looper() : inputBuffer (nullptr), pos (loopRange.getStart())
 -     {
 -     }
 - 
 -     Looper (const AudioBuffer<float>* buffer, Range<int64> range)
 -         : inputBuffer (buffer), loopRange (range), pos (range.getStart())
 -     {
 -     }
 - 
 -     void writeInto (AudioBuffer<float>& buffer)
 -     {
 -         if (loopRange.getLength() == 0)
 -             buffer.clear();
 - 
 -         const auto numChannelsToCopy = std::min (inputBuffer->getNumChannels(), buffer.getNumChannels());
 - 
 -         for (auto samplesCopied = 0; samplesCopied < buffer.getNumSamples();)
 -         {
 -             const auto numSamplesToCopy =
 -                 std::min (buffer.getNumSamples() - samplesCopied, (int) (loopRange.getEnd() - pos));
 - 
 -             for (int i = 0; i < numChannelsToCopy; ++i)
 -             {
 -                 buffer.copyFrom (i, samplesCopied, *inputBuffer, i, (int) pos, numSamplesToCopy);
 -             }
 - 
 -             samplesCopied += numSamplesToCopy;
 -             pos += numSamplesToCopy;
 - 
 -             jassert (pos <= loopRange.getEnd());
 - 
 -             if (pos == loopRange.getEnd())
 -                 pos = loopRange.getStart();
 -         }
 -     }
 - 
 - private:
 -     const AudioBuffer<float>* inputBuffer;
 -     Range<int64> loopRange;
 -     int64 pos;
 - };
 - 
 - class OptionalRange
 - {
 - public:
 -     using Type = Range<int64>;
 - 
 -     OptionalRange() : valid (false) {}
 -     explicit OptionalRange (Type valueIn) : valid (true), value (std::move (valueIn)) {}
 - 
 -     explicit operator bool() const noexcept { return valid; }
 - 
 -     const auto& operator*() const
 -     {
 -         jassert (valid);
 -         return value;
 -     }
 - 
 - private:
 -     bool valid;
 -     Type value;
 - };
 - 
 - //==============================================================================
 - // Returns the modified sample range in the output buffer.
 - inline OptionalRange readPlaybackRangeIntoBuffer (Range<double> playbackRange,
 -                                                   const ARAPlaybackRegion* playbackRegion,
 -                                                   AudioBuffer<float>& buffer,
 -                                                   const std::function<AudioFormatReader* (ARA::PlugIn::AudioSource*)>& getReader)
 - {
 -     const auto rangeInAudioModificationTime = playbackRange.movedToStartAt (playbackRange.getStart()
 -                                                                             - playbackRegion->getStartInAudioModificationTime());
 - 
 -     const auto audioSource = playbackRegion->getAudioModification()->getAudioSource();
 -     const auto audioModificationSampleRate = audioSource->getSampleRate();
 - 
 -     const Range<int64_t> sampleRangeInAudioModification {
 -         ARA::roundSamplePosition (rangeInAudioModificationTime.getStart() * audioModificationSampleRate),
 -         ARA::roundSamplePosition (rangeInAudioModificationTime.getEnd() * audioModificationSampleRate) - 1
 -     };
 - 
 -     const auto inputOffset = jlimit ((int64_t) 0, audioSource->getSampleCount(), sampleRangeInAudioModification.getStart());
 - 
 -     const auto outputOffset = -std::min (sampleRangeInAudioModification.getStart(), (int64_t) 0);
 - 
 -     /* TODO: Handle different AudioSource and playback sample rates.
 - 
 -        The conversion should be done inside a specialized AudioFormatReader so that we could use
 -        playbackSampleRate everywhere in this function and we could still read `readLength` number of samples
 -        from the source.
 - 
 -        The current implementation will be incorrect when sampling rates differ.
 -     */
 -     const auto readLength = [&]
 -     {
 -         const auto sourceReadLength =
 -             std::min (sampleRangeInAudioModification.getEnd(), audioSource->getSampleCount()) - inputOffset;
 - 
 -         const auto outputReadLength =
 -             std::min (outputOffset + sourceReadLength, (int64_t) buffer.getNumSamples()) - outputOffset;
 - 
 -         return std::min (sourceReadLength, outputReadLength);
 -     }();
 - 
 -     if (readLength == 0)
 -         return OptionalRange { {} };
 - 
 -     auto* reader = getReader (audioSource);
 - 
 -     if (reader != nullptr && reader->read (&buffer, (int) outputOffset, (int) readLength, inputOffset, true, true))
 -         return OptionalRange { { outputOffset, readLength } };
 - 
 -     return {};
 - }
 - 
 - class PossiblyBufferedReader
 - {
 - public:
 -     PossiblyBufferedReader() = default;
 - 
 -     explicit PossiblyBufferedReader (std::unique_ptr<BufferingAudioReader> readerIn)
 -         : setTimeoutFn ([ptr = readerIn.get()] (int ms) { ptr->setReadTimeout (ms); }),
 -           reader (std::move (readerIn))
 -     {}
 - 
 -     explicit PossiblyBufferedReader (std::unique_ptr<AudioFormatReader> readerIn)
 -         : setTimeoutFn(),
 -           reader (std::move (readerIn))
 -     {}
 - 
 -     void setReadTimeout (int ms)
 -     {
 -         NullCheckedInvocation::invoke (setTimeoutFn, ms);
 -     }
 - 
 -     AudioFormatReader* get() const { return reader.get(); }
 - 
 - private:
 -     std::function<void (int)> setTimeoutFn;
 -     std::unique_ptr<AudioFormatReader> reader;
 - };
 - 
 - //==============================================================================
 - class PlaybackRenderer  : public ARAPlaybackRenderer
 - {
 - public:
 -     using ARAPlaybackRenderer::ARAPlaybackRenderer;
 - 
 -     void prepareToPlay (double sampleRateIn,
 -                         int maximumSamplesPerBlockIn,
 -                         int numChannelsIn,
 -                         AudioProcessor::ProcessingPrecision,
 -                         AlwaysNonRealtime alwaysNonRealtime) override
 -     {
 -         numChannels = numChannelsIn;
 -         sampleRate = sampleRateIn;
 -         maximumSamplesPerBlock = maximumSamplesPerBlockIn;
 -         tempBuffer.reset (new AudioBuffer<float> (numChannels, maximumSamplesPerBlock));
 - 
 -         useBufferedAudioSourceReader = alwaysNonRealtime == AlwaysNonRealtime::no;
 - 
 -         for (const auto playbackRegion : getPlaybackRegions())
 -         {
 -             auto audioSource = playbackRegion->getAudioModification()->getAudioSource();
 - 
 -             if (audioSourceReaders.find (audioSource) == audioSourceReaders.end())
 -             {
 -                 auto reader = std::make_unique<ARAAudioSourceReader> (audioSource);
 - 
 -                 if (! useBufferedAudioSourceReader)
 -                 {
 -                     audioSourceReaders.emplace (audioSource,
 -                                                 PossiblyBufferedReader { std::move (reader) });
 -                 }
 -                 else
 -                 {
 -                     const auto readAheadSize = jmax (4 * maximumSamplesPerBlock,
 -                                                      roundToInt (2.0 * sampleRate));
 -                     audioSourceReaders.emplace (audioSource,
 -                                                 PossiblyBufferedReader { std::make_unique<BufferingAudioReader> (reader.release(),
 -                                                                                                                  *sharedTimesliceThread,
 -                                                                                                                  readAheadSize) });
 -                 }
 -             }
 -         }
 -     }
 - 
 -     void releaseResources() override
 -     {
 -         audioSourceReaders.clear();
 -         tempBuffer.reset();
 -     }
 - 
 -     bool processBlock (AudioBuffer<float>& buffer,
 -                        AudioProcessor::Realtime realtime,
 -                        const AudioPlayHead::PositionInfo& positionInfo) noexcept override
 -     {
 -         const auto numSamples = buffer.getNumSamples();
 -         jassert (numSamples <= maximumSamplesPerBlock);
 -         jassert (numChannels == buffer.getNumChannels());
 -         jassert (realtime == AudioProcessor::Realtime::no || useBufferedAudioSourceReader);
 -         const auto timeInSamples = positionInfo.getTimeInSamples().orFallback (0);
 -         const auto isPlaying = positionInfo.getIsPlaying();
 - 
 -         bool success = true;
 -         bool didRenderAnyRegion = false;
 - 
 -         if (isPlaying)
 -         {
 -             const auto blockRange = Range<int64>::withStartAndLength (timeInSamples, numSamples);
 - 
 -             for (const auto& playbackRegion : getPlaybackRegions())
 -             {
 -                 // Evaluate region borders in song time, calculate sample range to render in song time.
 -                 // Note that this example does not use head- or tailtime, so the includeHeadAndTail
 -                 // parameter is set to false here - this might need to be adjusted in actual plug-ins.
 -                 const auto playbackSampleRange = playbackRegion->getSampleRange (sampleRate, ARAPlaybackRegion::IncludeHeadAndTail::no);
 -                 auto renderRange = blockRange.getIntersectionWith (playbackSampleRange);
 - 
 -                 if (renderRange.isEmpty())
 -                     continue;
 - 
 -                 // Evaluate region borders in modification/source time and calculate offset between
 -                 // song and source samples, then clip song samples accordingly
 -                 // (if an actual plug-in supports time stretching, this must be taken into account here).
 -                 Range<int64> modificationSampleRange { playbackRegion->getStartInAudioModificationSamples(),
 -                                                        playbackRegion->getEndInAudioModificationSamples() };
 -                 const auto modificationSampleOffset = modificationSampleRange.getStart() - playbackSampleRange.getStart();
 - 
 -                 renderRange = renderRange.getIntersectionWith (modificationSampleRange.movedToStartAt (playbackSampleRange.getStart()));
 - 
 -                 if (renderRange.isEmpty())
 -                     continue;
 - 
 -                 // Get the audio source for the region and find the reader for that source.
 -                 // This simplified example code only produces audio if sample rate and channel count match -
 -                 // a robust plug-in would need to do conversion, see ARA SDK documentation.
 -                 const auto audioSource = playbackRegion->getAudioModification()->getAudioSource();
 -                 const auto readerIt = audioSourceReaders.find (audioSource);
 - 
 -                 if (std::make_tuple (audioSource->getChannelCount(), audioSource->getSampleRate()) != std::make_tuple (numChannels, sampleRate)
 -                     || (readerIt == audioSourceReaders.end()))
 -                 {
 -                     success = false;
 -                     continue;
 -                 }
 - 
 -                 auto& reader = readerIt->second;
 -                 reader.setReadTimeout (realtime == AudioProcessor::Realtime::no ? 100 : 0);
 - 
 -                 // Calculate buffer offsets.
 -                 const int numSamplesToRead = (int) renderRange.getLength();
 -                 const int startInBuffer = (int) (renderRange.getStart() - blockRange.getStart());
 -                 auto startInSource = renderRange.getStart() + modificationSampleOffset;
 - 
 -                 // Read samples:
 -                 // first region can write directly into output, later regions need to use local buffer.
 -                 auto& readBuffer = (didRenderAnyRegion) ? *tempBuffer : buffer;
 - 
 -                 if (! reader.get()->read (&readBuffer, startInBuffer, numSamplesToRead, startInSource, true, true))
 -                 {
 -                     success = false;
 -                     continue;
 -                 }
 - 
 -                 if (didRenderAnyRegion)
 -                 {
 -                     // Mix local buffer into the output buffer.
 -                     for (int c = 0; c < numChannels; ++c)
 -                         buffer.addFrom (c, startInBuffer, *tempBuffer, c, startInBuffer, numSamplesToRead);
 -                 }
 -                 else
 -                 {
 -                     // Clear any excess at start or end of the region.
 -                     if (startInBuffer != 0)
 -                         buffer.clear (0, startInBuffer);
 - 
 -                     const int endInBuffer = startInBuffer + numSamplesToRead;
 -                     const int remainingSamples = numSamples - endInBuffer;
 - 
 -                     if (remainingSamples != 0)
 -                         buffer.clear (endInBuffer, remainingSamples);
 - 
 -                     didRenderAnyRegion = true;
 -                 }
 -             }
 -         }
 - 
 -         // If no playback or no region did intersect, clear buffer now.
 -         if (! didRenderAnyRegion)
 -             buffer.clear();
 - 
 -         return success;
 -     }
 - 
 - private:
 -     //==============================================================================
 -     // We're subclassing here only to provide a proper default c'tor for our shared resource
 - 
 -     SharedResourcePointer<SharedTimeSliceThread> sharedTimesliceThread;
 -     std::map<ARA::PlugIn::AudioSource*, PossiblyBufferedReader> audioSourceReaders;
 -     bool useBufferedAudioSourceReader = true;
 -     int numChannels = 2;
 -     double sampleRate = 48000.0;
 -     int maximumSamplesPerBlock = 128;
 -     std::unique_ptr<AudioBuffer<float>> tempBuffer;
 - };
 - 
 - class EditorRenderer  : public ARAEditorRenderer,
 -                         private ARARegionSequence::Listener
 - {
 - public:
 -     EditorRenderer (ARA::PlugIn::DocumentController* documentController, const PreviewState* previewStateIn)
 -         : ARAEditorRenderer (documentController), previewState (previewStateIn), previewBuffer()
 -     {
 -         jassert (previewState != nullptr);
 -     }
 - 
 -     ~EditorRenderer() override
 -     {
 -         for (const auto& rs : regionSequences)
 -             rs->removeListener (this);
 -     }
 - 
 -     void didAddPlaybackRegionToRegionSequence (ARARegionSequence*, ARAPlaybackRegion*) override
 -     {
 -         asyncConfigCallback.startConfigure();
 -     }
 - 
 -     void didAddRegionSequence (ARA::PlugIn::RegionSequence* rs) noexcept override
 -     {
 -         auto* sequence = dynamic_cast<ARARegionSequence*> (rs);
 -         sequence->addListener (this);
 -         regionSequences.insert (sequence);
 -         asyncConfigCallback.startConfigure();
 -     }
 - 
 -     void didAddPlaybackRegion (ARA::PlugIn::PlaybackRegion*) noexcept override
 -     {
 -         asyncConfigCallback.startConfigure();
 -     }
 - 
 -     /*  An ARA host could be using either the `addPlaybackRegion()` or `addRegionSequence()` interface
 -         so we need to check the other side of both.
 - 
 -         The callback must have a signature of `bool (ARAPlaybackRegion*)`
 -     */
 -     template <typename Callback>
 -     void forEachPlaybackRegion (Callback&& cb)
 -     {
 -         for (const auto& playbackRegion : getPlaybackRegions())
 -             if (! cb (playbackRegion))
 -                 return;
 - 
 -         for (const auto& regionSequence : getRegionSequences())
 -             for (const auto& playbackRegion : regionSequence->getPlaybackRegions())
 -                 if (! cb (playbackRegion))
 -                     return;
 -     }
 - 
 -     void prepareToPlay (double sampleRateIn,
 -                         int maximumExpectedSamplesPerBlock,
 -                         int numChannels,
 -                         AudioProcessor::ProcessingPrecision,
 -                         AlwaysNonRealtime alwaysNonRealtime) override
 -     {
 -         sampleRate = sampleRateIn;
 -         previewBuffer = std::make_unique<AudioBuffer<float>> (numChannels, (int) (2 * sampleRateIn));
 - 
 -         ignoreUnused (maximumExpectedSamplesPerBlock, alwaysNonRealtime);
 -     }
 - 
 -     void releaseResources() override
 -     {
 -         audioSourceReaders.clear();
 -     }
 - 
 -     void reset() override
 -     {
 -         previewBuffer->clear();
 -     }
 - 
 -     bool processBlock (AudioBuffer<float>& buffer,
 -                        AudioProcessor::Realtime realtime,
 -                        const AudioPlayHead::PositionInfo& positionInfo) noexcept override
 -     {
 -         ignoreUnused (realtime);
 - 
 -         return asyncConfigCallback.withLock ([&] (bool locked)
 -         {
 -             if (! locked)
 -                 return true;
 - 
 -             if (positionInfo.getIsPlaying())
 -                 return true;
 - 
 -             if (const auto previewedRegion = previewState->previewedRegion.load())
 -             {
 -                 const auto regionIsAssignedToEditor = [&]()
 -                 {
 -                     bool regionIsAssigned = false;
 - 
 -                     forEachPlaybackRegion ([&previewedRegion, ®ionIsAssigned] (const auto& region)
 -                     {
 -                         if (region == previewedRegion)
 -                         {
 -                             regionIsAssigned = true;
 -                             return false;
 -                         }
 - 
 -                         return true;
 -                     });
 - 
 -                     return regionIsAssigned;
 -                 }();
 - 
 -                 if (regionIsAssignedToEditor)
 -                 {
 -                     const auto previewTime = previewState->previewTime.load();
 - 
 -                     if (lastPreviewTime != previewTime || lastPlaybackRegion != previewedRegion)
 -                     {
 -                         Range<double> previewRangeInPlaybackTime { previewTime - 0.25, previewTime + 0.25 };
 -                         previewBuffer->clear();
 -                         const auto rangeInOutput = readPlaybackRangeIntoBuffer (previewRangeInPlaybackTime,
 -                                                                                 previewedRegion,
 -                                                                                 *previewBuffer,
 -                                                                                 [this] (auto* source) -> auto*
 -                                                                                 {
 -                                                                                     const auto iter = audioSourceReaders.find (source);
 -                                                                                     return iter != audioSourceReaders.end() ? iter->second.get() : nullptr;
 -                                                                                 });
 - 
 -                         if (rangeInOutput)
 -                         {
 -                             lastPreviewTime = previewTime;
 -                             lastPlaybackRegion = previewedRegion;
 -                             previewLooper = Looper (previewBuffer.get(), *rangeInOutput);
 -                         }
 -                     }
 -                     else
 -                     {
 -                         previewLooper.writeInto (buffer);
 -                     }
 -                 }
 -             }
 - 
 -             return true;
 -         });
 -     }
 - 
 - private:
 -     void configure()
 -     {
 -         forEachPlaybackRegion ([this, maximumExpectedSamplesPerBlock = 1000] (const auto& playbackRegion)
 -         {
 -             const auto audioSource = playbackRegion->getAudioModification()->getAudioSource();
 - 
 -             if (audioSourceReaders.find (audioSource) == audioSourceReaders.end())
 -             {
 -                 audioSourceReaders[audioSource] = std::make_unique<BufferingAudioReader> (
 -                         new ARAAudioSourceReader (playbackRegion->getAudioModification()->getAudioSource()),
 -                         *timeSliceThread,
 -                         std::max (4 * maximumExpectedSamplesPerBlock, (int) sampleRate));
 -             }
 - 
 -             return true;
 -         });
 -     }
 - 
 -     const PreviewState* previewState = nullptr;
 -     AsyncConfigurationCallback asyncConfigCallback { [this] { configure(); } };
 -     double lastPreviewTime = 0.0;
 -     ARAPlaybackRegion* lastPlaybackRegion = nullptr;
 -     std::unique_ptr<AudioBuffer<float>> previewBuffer;
 -     Looper previewLooper;
 - 
 -     double sampleRate = 48000.0;
 -     SharedResourcePointer<SharedTimeSliceThread> timeSliceThread;
 -     std::map<ARA::PlugIn::AudioSource*, std::unique_ptr<BufferingAudioReader>> audioSourceReaders;
 - 
 -     std::set<ARARegionSequence*> regionSequences;
 - };
 - 
 - //==============================================================================
 - class ARADemoPluginDocumentControllerSpecialisation  : public ARADocumentControllerSpecialisation
 - {
 - public:
 -     using ARADocumentControllerSpecialisation::ARADocumentControllerSpecialisation;
 - 
 -     PreviewState previewState;
 - 
 - protected:
 -     ARAPlaybackRenderer* doCreatePlaybackRenderer() noexcept override
 -     {
 -         return new PlaybackRenderer (getDocumentController());
 -     }
 - 
 -     EditorRenderer* doCreateEditorRenderer() noexcept override
 -     {
 -         return new EditorRenderer (getDocumentController(), &previewState);
 -     }
 - 
 -     bool doRestoreObjectsFromStream (ARAInputStream& input,
 -                                      const ARARestoreObjectsFilter* filter) noexcept override
 -     {
 -         ignoreUnused (input, filter);
 -         return false;
 -     }
 - 
 -     bool doStoreObjectsToStream (ARAOutputStream& output, const ARAStoreObjectsFilter* filter) noexcept override
 -     {
 -         ignoreUnused (output, filter);
 -         return false;
 -     }
 - };
 - 
 - //==============================================================================
 - class ARADemoPluginAudioProcessorImpl  : public AudioProcessor,
 -                                          public AudioProcessorARAExtension
 - {
 - public:
 -     //==============================================================================
 -     ARADemoPluginAudioProcessorImpl()
 -         : AudioProcessor (getBusesProperties())
 -     {}
 - 
 -     ~ARADemoPluginAudioProcessorImpl() override = default;
 - 
 -     //==============================================================================
 -     void prepareToPlay (double sampleRate, int samplesPerBlock) override
 -     {
 -          prepareToPlayForARA (sampleRate, samplesPerBlock, getMainBusNumOutputChannels(), getProcessingPrecision());
 -     }
 - 
 -     void releaseResources() override
 -     {
 -         releaseResourcesForARA();
 -     }
 - 
 -     bool isBusesLayoutSupported (const BusesLayout& layouts) const override
 -     {
 -         if (layouts.getMainOutputChannelSet() != AudioChannelSet::mono()
 -             && layouts.getMainOutputChannelSet() != AudioChannelSet::stereo())
 -             return false;
 - 
 -         return true;
 -     }
 - 
 -     void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override
 -     {
 -         ignoreUnused (midiMessages);
 - 
 -         ScopedNoDenormals noDenormals;
 - 
 -         if (! processBlockForARA (buffer, isRealtime(), getPlayHead()))
 -             processBlockBypassed (buffer, midiMessages);
 -     }
 - 
 -     //==============================================================================
 -     const String getName() const override                             { return "ARAPluginDemo"; }
 -     bool acceptsMidi() const override                                 { return true; }
 -     bool producesMidi() const override                                { return true; }
 -     double getTailLengthSeconds() const override                      { return 0.0; }
 - 
 -     //==============================================================================
 -     int getNumPrograms() override                                     { return 0; }
 -     int getCurrentProgram() override                                  { return 0; }
 -     void setCurrentProgram (int) override                             {}
 -     const String getProgramName (int) override                        { return "None"; }
 -     void changeProgramName (int, const String&) override              {}
 - 
 -     //==============================================================================
 -     void getStateInformation (MemoryBlock&) override                  {}
 -     void setStateInformation (const void*, int) override              {}
 - 
 - private:
 -     //==============================================================================
 -     static BusesProperties getBusesProperties()
 -     {
 -         return BusesProperties().withInput  ("Input",  AudioChannelSet::stereo(), true)
 -             .withOutput ("Output", AudioChannelSet::stereo(), true);
 -     }
 - 
 -     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARADemoPluginAudioProcessorImpl)
 - };
 - 
 - //==============================================================================
 - struct WaveformCache : private ARAAudioSource::Listener
 - {
 -     WaveformCache() : thumbnailCache (20)
 -     {
 -     }
 - 
 -     ~WaveformCache() override
 -     {
 -         for (const auto& entry : thumbnails)
 -         {
 -             entry.first->removeListener (this);
 -         }
 -     }
 - 
 -     //==============================================================================
 -     void willDestroyAudioSource (ARAAudioSource* audioSource) override
 -     {
 -         removeAudioSource (audioSource);
 -     }
 - 
 -     AudioThumbnail& getOrCreateThumbnail (ARAAudioSource* audioSource)
 -     {
 -         const auto iter = thumbnails.find (audioSource);
 - 
 -         if (iter != std::end (thumbnails))
 -             return *iter->second;
 - 
 -         auto thumb = std::make_unique<AudioThumbnail> (128, dummyManager, thumbnailCache);
 -         auto& result = *thumb;
 - 
 -         ++hash;
 -         thumb->setReader (new ARAAudioSourceReader (audioSource), hash);
 - 
 -         audioSource->addListener (this);
 -         thumbnails.emplace (audioSource, std::move (thumb));
 -         return result;
 -     }
 - 
 - private:
 -     void removeAudioSource (ARAAudioSource* audioSource)
 -     {
 -         audioSource->removeListener (this);
 -         thumbnails.erase (audioSource);
 -     }
 - 
 -     int64 hash = 0;
 -     AudioFormatManager dummyManager;
 -     AudioThumbnailCache thumbnailCache;
 -     std::map<ARAAudioSource*, std::unique_ptr<AudioThumbnail>> thumbnails;
 - };
 - 
 - class PlaybackRegionView : public Component,
 -                            public ChangeListener
 - {
 - public:
 -     PlaybackRegionView (ARAPlaybackRegion& region, WaveformCache& cache)
 -         : playbackRegion (region), waveformCache (cache)
 -     {
 -         auto* audioSource = playbackRegion.getAudioModification()->getAudioSource();
 - 
 -         waveformCache.getOrCreateThumbnail (audioSource).addChangeListener (this);
 -     }
 - 
 -     ~PlaybackRegionView() override
 -     {
 -         waveformCache.getOrCreateThumbnail (playbackRegion.getAudioModification()->getAudioSource())
 -             .removeChangeListener (this);
 -     }
 - 
 -     void mouseDown (const MouseEvent& m) override
 -     {
 -         const auto relativeTime = (double) m.getMouseDownX() / getLocalBounds().getWidth();
 -         const auto previewTime = playbackRegion.getStartInPlaybackTime()
 -                                  + relativeTime * playbackRegion.getDurationInPlaybackTime();
 -         auto& previewState = ARADocumentControllerSpecialisation::getSpecialisedDocumentController<ARADemoPluginDocumentControllerSpecialisation> (playbackRegion.getDocumentController())->previewState;
 -         previewState.previewTime.store (previewTime);
 -         previewState.previewedRegion.store (&playbackRegion);
 -     }
 - 
 -     void mouseUp (const MouseEvent&) override
 -     {
 -         auto& previewState = ARADocumentControllerSpecialisation::getSpecialisedDocumentController<ARADemoPluginDocumentControllerSpecialisation> (playbackRegion.getDocumentController())->previewState;
 -         previewState.previewTime.store (0.0);
 -         previewState.previewedRegion.store (nullptr);
 -     }
 - 
 -     void changeListenerCallback (ChangeBroadcaster*) override
 -     {
 -         repaint();
 -     }
 - 
 -     void paint (Graphics& g) override
 -     {
 -         g.fillAll (Colours::white.darker());
 -         g.setColour (Colours::darkgrey.darker());
 -         auto& thumbnail = waveformCache.getOrCreateThumbnail (playbackRegion.getAudioModification()->getAudioSource());
 -         thumbnail.drawChannels (g,
 -                                 getLocalBounds(),
 -                                 playbackRegion.getStartInAudioModificationTime(),
 -                                 playbackRegion.getEndInAudioModificationTime(),
 -                                 1.0f);
 -         g.setColour (Colours::black);
 -         g.drawRect (getLocalBounds());
 -     }
 - 
 -     void resized() override
 -     {
 -         repaint();
 -     }
 - 
 - private:
 -     ARAPlaybackRegion& playbackRegion;
 -     WaveformCache& waveformCache;
 - };
 - 
 - class RegionSequenceView : public Component,
 -                            public ARARegionSequence::Listener,
 -                            public ChangeBroadcaster,
 -                            private ARAPlaybackRegion::Listener
 - {
 - public:
 -     RegionSequenceView (ARARegionSequence& rs, WaveformCache& cache, double pixelPerSec)
 -         : regionSequence (rs), waveformCache (cache), zoomLevelPixelPerSecond (pixelPerSec)
 -     {
 -         regionSequence.addListener (this);
 - 
 -         for (auto* playbackRegion : regionSequence.getPlaybackRegions())
 -             createAndAddPlaybackRegionView (playbackRegion);
 - 
 -         updatePlaybackDuration();
 -     }
 - 
 -     ~RegionSequenceView() override
 -     {
 -         regionSequence.removeListener (this);
 - 
 -         for (const auto& it : playbackRegionViews)
 -             it.first->removeListener (this);
 -     }
 - 
 -     //==============================================================================
 -     // ARA Document change callback overrides
 -     void willRemovePlaybackRegionFromRegionSequence (ARARegionSequence*,
 -                                                      ARAPlaybackRegion* playbackRegion) override
 -     {
 -         playbackRegion->removeListener (this);
 -         removeChildComponent (playbackRegionViews[playbackRegion].get());
 -         playbackRegionViews.erase (playbackRegion);
 -         updatePlaybackDuration();
 -     }
 - 
 -     void didAddPlaybackRegionToRegionSequence (ARARegionSequence*, ARAPlaybackRegion* playbackRegion) override
 -     {
 -         createAndAddPlaybackRegionView (playbackRegion);
 -         updatePlaybackDuration();
 -     }
 - 
 -     void willDestroyPlaybackRegion (ARAPlaybackRegion* playbackRegion) override
 -     {
 -         playbackRegion->removeListener (this);
 -         removeChildComponent (playbackRegionViews[playbackRegion].get());
 -         playbackRegionViews.erase (playbackRegion);
 -         updatePlaybackDuration();
 -     }
 - 
 -     void willUpdatePlaybackRegionProperties (ARAPlaybackRegion*, ARAPlaybackRegion::PropertiesPtr) override
 -     {
 -     }
 - 
 -     void didUpdatePlaybackRegionProperties (ARAPlaybackRegion*) override
 -     {
 -         updatePlaybackDuration();
 -     }
 - 
 -     void resized() override
 -     {
 -         for (auto& pbr : playbackRegionViews)
 -         {
 -             const auto playbackRegion = pbr.first;
 -             pbr.second->setBounds (
 -                 getLocalBounds()
 -                     .withTrimmedLeft (roundToInt (playbackRegion->getStartInPlaybackTime() * zoomLevelPixelPerSecond))
 -                     .withWidth (roundToInt (playbackRegion->getDurationInPlaybackTime() * zoomLevelPixelPerSecond)));
 -         }
 -     }
 - 
 -     auto getPlaybackDuration() const noexcept
 -     {
 -         return playbackDuration;
 -     }
 - 
 -     void setZoomLevel (double pixelPerSecond)
 -     {
 -         zoomLevelPixelPerSecond = pixelPerSecond;
 -         resized();
 -     }
 - 
 - private:
 -     void createAndAddPlaybackRegionView (ARAPlaybackRegion* playbackRegion)
 -     {
 -         playbackRegionViews[playbackRegion] = std::make_unique<PlaybackRegionView> (*playbackRegion, waveformCache);
 -         playbackRegion->addListener (this);
 -         addAndMakeVisible (*playbackRegionViews[playbackRegion]);
 -     }
 - 
 -     void updatePlaybackDuration()
 -     {
 -         const auto iter = std::max_element (
 -             playbackRegionViews.begin(),
 -             playbackRegionViews.end(),
 -             [] (const auto& a, const auto& b) { return a.first->getEndInPlaybackTime() < b.first->getEndInPlaybackTime(); });
 - 
 -         playbackDuration = iter != playbackRegionViews.end() ? iter->first->getEndInPlaybackTime()
 -                                                              : 0.0;
 - 
 -         sendChangeMessage();
 -     }
 - 
 -     ARARegionSequence& regionSequence;
 -     WaveformCache& waveformCache;
 -     std::unordered_map<ARAPlaybackRegion*, std::unique_ptr<PlaybackRegionView>> playbackRegionViews;
 -     double playbackDuration = 0.0;
 -     double zoomLevelPixelPerSecond;
 - };
 - 
 - class ZoomControls : public Component
 - {
 - public:
 -     ZoomControls()
 -     {
 -         addAndMakeVisible (zoomInButton);
 -         addAndMakeVisible (zoomOutButton);
 -     }
 - 
 -     void setZoomInCallback  (std::function<void()> cb)   { zoomInButton.onClick  = std::move (cb); }
 -     void setZoomOutCallback (std::function<void()> cb)   { zoomOutButton.onClick = std::move (cb); }
 - 
 -     void resized() override
 -     {
 -         FlexBox fb;
 -         fb.justifyContent = FlexBox::JustifyContent::flexEnd;
 - 
 -         for (auto* button : { &zoomInButton, &zoomOutButton })
 -             fb.items.add (FlexItem (*button).withMinHeight (30.0f).withMinWidth (30.0f).withMargin ({ 5, 5, 5, 0 }));
 - 
 -         fb.performLayout (getLocalBounds());
 -     }
 - 
 - private:
 -     TextButton zoomInButton { "+" }, zoomOutButton { "-" };
 - };
 - 
 - class TrackHeader : public Component
 - {
 - public:
 -     explicit TrackHeader (const ARARegionSequence& regionSequenceIn) : regionSequence (regionSequenceIn)
 -     {
 -         update();
 - 
 -         addAndMakeVisible (trackNameLabel);
 -     }
 - 
 -     void resized() override
 -     {
 -         trackNameLabel.setBounds (getLocalBounds().reduced (2));
 -     }
 - 
 -     void paint (Graphics& g) override
 -     {
 -         g.setColour (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
 -         g.fillRoundedRectangle (getLocalBounds().reduced (2).toType<float>(), 6.0f);
 -         g.setColour (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).contrasting());
 -         g.drawRoundedRectangle (getLocalBounds().reduced (2).toType<float>(), 6.0f, 1.0f);
 -     }
 - 
 - private:
 -     void update()
 -     {
 -         const auto getWithDefaultValue =
 -             [] (const ARA::PlugIn::OptionalProperty<ARA::ARAUtf8String>& optional, String defaultValue)
 -         {
 -             if (const ARA::ARAUtf8String value = optional)
 -                 return String (value);
 - 
 -             return defaultValue;
 -         };
 - 
 -         trackNameLabel.setText (getWithDefaultValue (regionSequence.getName(), "No track name"),
 -                                 NotificationType::dontSendNotification);
 -     }
 - 
 -     const ARARegionSequence& regionSequence;
 -     Label trackNameLabel;
 - };
 - 
 - constexpr auto trackHeight = 60;
 - 
 - class VerticalLayoutViewportContent : public Component
 - {
 - public:
 -     void resized() override
 -     {
 -         auto bounds = getLocalBounds();
 - 
 -         for (auto* component : getChildren())
 -         {
 -             component->setBounds (bounds.removeFromTop (trackHeight));
 -             component->resized();
 -         }
 -     }
 - 
 -     void setOverlayComponent (Component* component)
 -     {
 -         if (overlayComponent != nullptr && overlayComponent != component)
 -             removeChildComponent (overlayComponent);
 - 
 -         addChildComponent (component);
 -         overlayComponent = component;
 -     }
 - 
 - private:
 -     Component* overlayComponent = nullptr;
 - };
 - 
 - class VerticalLayoutViewport : public Viewport
 - {
 - public:
 -     VerticalLayoutViewport()
 -     {
 -         setViewedComponent (&content, false);
 -     }
 - 
 -     void paint (Graphics& g) override
 -     {
 -         g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).brighter());
 -     }
 - 
 -     std::function<void (Rectangle<int>)> onVisibleAreaChanged;
 - 
 -     VerticalLayoutViewportContent content;
 - 
 - private:
 -     void visibleAreaChanged (const Rectangle<int>& newVisibleArea) override
 -     {
 -         NullCheckedInvocation::invoke (onVisibleAreaChanged, newVisibleArea);
 -     }
 - };
 - 
 - class OverlayComponent : public Component,
 -                          private Timer
 - {
 - public:
 -     class PlayheadMarkerComponent : public Component
 -     {
 -         void paint (Graphics& g) override { g.fillAll (juce::Colours::yellow.darker (0.2f)); }
 -     };
 - 
 -     OverlayComponent(std::function<AudioPlayHead*()> getAudioPlayheadIn)
 -         : getAudioPlayhead (std::move (getAudioPlayheadIn))
 -     {
 -         addChildComponent (playheadMarker);
 -         setInterceptsMouseClicks (false, false);
 -         startTimerHz (30);
 -     }
 - 
 -     ~OverlayComponent() override
 -     {
 -         stopTimer();
 -     }
 - 
 -     void resized() override
 -     {
 -         doResize();
 -     }
 - 
 -     void setZoomLevel (double pixelPerSecondIn)
 -     {
 -         pixelPerSecond = pixelPerSecondIn;
 -     }
 - 
 -     void setHorizontalOffset (int offset)
 -     {
 -         horizontalOffset = offset;
 -     }
 - 
 - private:
 -     void doResize()
 -     {
 -         auto* aph = getAudioPlayhead();
 -         const auto info = aph->getPosition();
 - 
 -         if (info.hasValue() && info->getIsPlaying())
 -         {
 -             const auto markerX = info->getTimeInSeconds().orFallback (0) * pixelPerSecond;
 -             const auto playheadLine = getLocalBounds().withTrimmedLeft ((int) (markerX - markerWidth / 2.0) - horizontalOffset)
 -                                                       .removeFromLeft ((int) markerWidth);
 -             playheadMarker.setVisible (true);
 -             playheadMarker.setBounds (playheadLine);
 -         }
 -         else
 -         {
 -             playheadMarker.setVisible (false);
 -         }
 -     }
 - 
 -     void timerCallback() override
 -     {
 -         doResize();
 -     }
 - 
 -     static constexpr double markerWidth = 2.0;
 - 
 -     std::function<AudioPlayHead*()> getAudioPlayhead;
 -     double pixelPerSecond = 1.0;
 -     int horizontalOffset = 0;
 -     PlayheadMarkerComponent playheadMarker;
 - };
 - 
 - class DocumentView  : public Component,
 -                       public ChangeListener,
 -                       private ARADocument::Listener,
 -                       private ARAEditorView::Listener
 - {
 - public:
 -     explicit DocumentView (ARADocument& document, std::function<AudioPlayHead*()> getAudioPlayhead)
 -         : araDocument (document),
 -           overlay (std::move (getAudioPlayhead))
 -     {
 -         addAndMakeVisible (tracksBackground);
 - 
 -         viewport.onVisibleAreaChanged = [this] (const auto& r)
 -         {
 -             viewportHeightOffset = r.getY();
 -             overlay.setHorizontalOffset (r.getX());
 -             resized();
 -         };
 - 
 -         addAndMakeVisible (viewport);
 - 
 -         overlay.setZoomLevel (zoomLevelPixelPerSecond);
 -         addAndMakeVisible (overlay);
 - 
 -         zoomControls.setZoomInCallback  ([this] { zoom (2.0); });
 -         zoomControls.setZoomOutCallback ([this] { zoom (0.5); });
 -         addAndMakeVisible (zoomControls);
 - 
 -         invalidateRegionSequenceViews();
 -         araDocument.addListener (this);
 -     }
 - 
 -     ~DocumentView() override
 -     {
 -         araDocument.removeListener (this);
 -     }
 - 
 -     //==============================================================================
 -     // ARADocument::Listener overrides
 -     void didReorderRegionSequencesInDocument (ARADocument*) override
 -     {
 -         invalidateRegionSequenceViews();
 -     }
 - 
 -     void didAddRegionSequenceToDocument (ARADocument*, ARARegionSequence*) override
 -     {
 -         invalidateRegionSequenceViews();
 -     }
 - 
 -     void willRemoveRegionSequenceFromDocument (ARADocument*, ARARegionSequence* regionSequence) override
 -     {
 -         removeRegionSequenceView (regionSequence);
 -     }
 - 
 -     void didEndEditing (ARADocument*) override
 -     {
 -         rebuildRegionSequenceViews();
 -         update();
 -     }
 - 
 -     //==============================================================================
 -     void changeListenerCallback (ChangeBroadcaster*) override
 -     {
 -         update();
 -     }
 - 
 -     //==============================================================================
 -     // ARAEditorView::Listener overrides
 -     void onNewSelection (const ARA::PlugIn::ViewSelection&) override
 -     {
 -     }
 - 
 -     void onHideRegionSequences (const std::vector<ARARegionSequence*>&) override
 -     {
 -     }
 - 
 -     //==============================================================================
 -     void paint (Graphics& g) override
 -     {
 -         g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).darker());
 -     }
 - 
 -     void resized() override
 -     {
 -         auto bounds = getLocalBounds();
 -         const auto bottomControlsBounds = bounds.removeFromBottom (40);
 -         const auto headerBounds = bounds.removeFromLeft (headerWidth).reduced (2);
 - 
 -         zoomControls.setBounds (bottomControlsBounds);
 -         layOutVertically (headerBounds, trackHeaders, viewportHeightOffset);
 -         tracksBackground.setBounds (bounds);
 -         viewport.setBounds (bounds);
 -         overlay.setBounds (bounds);
 -     }
 - 
 -     //==============================================================================
 -     void setZoomLevel (double pixelPerSecond)
 -     {
 -         zoomLevelPixelPerSecond = pixelPerSecond;
 - 
 -         for (const auto& view : regionSequenceViews)
 -             view.second->setZoomLevel (zoomLevelPixelPerSecond);
 - 
 -         overlay.setZoomLevel (zoomLevelPixelPerSecond);
 - 
 -         update();
 -     }
 - 
 -     static constexpr int headerWidth = 120;
 - 
 - private:
 -     struct RegionSequenceViewKey
 -     {
 -         explicit RegionSequenceViewKey (ARARegionSequence* regionSequence)
 -             : orderIndex (regionSequence->getOrderIndex()), sequence (regionSequence)
 -         {
 -         }
 - 
 -         bool operator< (const RegionSequenceViewKey& other) const
 -         {
 -             return std::tie (orderIndex, sequence) < std::tie (other.orderIndex, other.sequence);
 -         }
 - 
 -         ARA::ARAInt32 orderIndex;
 -         ARARegionSequence* sequence;
 -     };
 - 
 -     void zoom (double factor)
 -     {
 -         zoomLevelPixelPerSecond = jlimit (minimumZoom, minimumZoom * 32, zoomLevelPixelPerSecond * factor);
 -         setZoomLevel (zoomLevelPixelPerSecond);
 -     }
 - 
 -     template <typename T>
 -     void layOutVertically (Rectangle<int> bounds, T& components, int verticalOffset = 0)
 -     {
 -         bounds = bounds.withY (bounds.getY() - verticalOffset).withHeight (bounds.getHeight() + verticalOffset);
 - 
 -         for (auto& component : components)
 -         {
 -             component.second->setBounds (bounds.removeFromTop (trackHeight));
 -             component.second->resized();
 -         }
 -     }
 - 
 -     void update()
 -     {
 -         timelineLength = 0.0;
 - 
 -         for (const auto& view : regionSequenceViews)
 -             timelineLength = std::max (timelineLength, view.second->getPlaybackDuration());
 - 
 -         const Rectangle<int> timelineSize (roundToInt (timelineLength * zoomLevelPixelPerSecond),
 -                                            (int) regionSequenceViews.size() * trackHeight);
 -         viewport.content.setSize (timelineSize.getWidth(), timelineSize.getHeight());
 -         viewport.content.resized();
 - 
 -         resized();
 -     }
 - 
 -     void addTrackViews (ARARegionSequence* regionSequence)
 -     {
 -         const auto insertIntoMap = [](auto& map, auto key, auto value) -> auto&
 -         {
 -             auto it = map.insert ({ std::move (key), std::move (value) });
 -             return *(it.first->second);
 -         };
 - 
 -         auto& regionSequenceView = insertIntoMap (
 -             regionSequenceViews,
 -             RegionSequenceViewKey { regionSequence },
 -             std::make_unique<RegionSequenceView> (*regionSequence, waveformCache, zoomLevelPixelPerSecond));
 - 
 -         regionSequenceView.addChangeListener (this);
 -         viewport.content.addAndMakeVisible (regionSequenceView);
 - 
 -         auto& trackHeader = insertIntoMap (trackHeaders,
 -                                            RegionSequenceViewKey { regionSequence },
 -                                            std::make_unique<TrackHeader> (*regionSequence));
 - 
 -         addAndMakeVisible (trackHeader);
 -     }
 - 
 -     void removeRegionSequenceView (ARARegionSequence* regionSequence)
 -     {
 -         const auto& view = regionSequenceViews.find (RegionSequenceViewKey { regionSequence });
 - 
 -         if (view != regionSequenceViews.cend())
 -         {
 -             removeChildComponent (view->second.get());
 -             regionSequenceViews.erase (view);
 -         }
 - 
 -         invalidateRegionSequenceViews();
 -     }
 - 
 -     void invalidateRegionSequenceViews()
 -     {
 -         regionSequenceViewsAreValid = false;
 -         rebuildRegionSequenceViews();
 -     }
 - 
 -     void rebuildRegionSequenceViews()
 -     {
 -         if (! regionSequenceViewsAreValid && ! araDocument.getDocumentController()->isHostEditingDocument())
 -         {
 -             for (auto& view : regionSequenceViews)
 -                 removeChildComponent (view.second.get());
 - 
 -             regionSequenceViews.clear();
 - 
 -             for (auto& view : trackHeaders)
 -                 removeChildComponent (view.second.get());
 - 
 -             trackHeaders.clear();
 - 
 -             for (auto* regionSequence : araDocument.getRegionSequences())
 -             {
 -                 addTrackViews (regionSequence);
 -             }
 - 
 -             update();
 - 
 -             regionSequenceViewsAreValid = true;
 -         }
 -     }
 - 
 -     class TracksBackgroundComponent : public Component
 -     {
 -         void paint (Graphics& g) override
 -         {
 -             g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).brighter());
 -         }
 -     };
 - 
 -     static constexpr auto minimumZoom = 10.0;
 -     static constexpr auto trackHeight = 60;
 - 
 -     ARADocument& araDocument;
 - 
 -     bool regionSequenceViewsAreValid = false;
 -     double timelineLength = 0;
 -     double zoomLevelPixelPerSecond = minimumZoom * 4;
 - 
 -     WaveformCache waveformCache;
 -     TracksBackgroundComponent tracksBackground;
 -     std::map<RegionSequenceViewKey, std::unique_ptr<TrackHeader>> trackHeaders;
 -     std::map<RegionSequenceViewKey, std::unique_ptr<RegionSequenceView>> regionSequenceViews;
 -     VerticalLayoutViewport viewport;
 -     OverlayComponent overlay;
 -     ZoomControls zoomControls;
 - 
 -     int viewportHeightOffset = 0;
 - };
 - 
 - 
 - class ARADemoPluginProcessorEditor  : public AudioProcessorEditor,
 -                                       public AudioProcessorEditorARAExtension
 - {
 - public:
 -     explicit ARADemoPluginProcessorEditor (ARADemoPluginAudioProcessorImpl& p)
 -         : AudioProcessorEditor (&p),
 -           AudioProcessorEditorARAExtension (&p)
 -     {
 -         if (auto* editorView = getARAEditorView())
 -         {
 -             auto* document = ARADocumentControllerSpecialisation::getSpecialisedDocumentController(editorView->getDocumentController())->getDocument();
 -             documentView = std::make_unique<DocumentView> (*document,
 -                                                            [this]() { return getAudioProcessor()->getPlayHead(); });
 -         }
 - 
 -         addAndMakeVisible (documentView.get());
 - 
 -         // ARA requires that plugin editors are resizable to support tight integration
 -         // into the host UI
 -         setResizable (true, false);
 -         setSize (400, 300);
 -     }
 - 
 -     //==============================================================================
 -     void paint (Graphics& g) override
 -     {
 -         g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
 - 
 -         if (! isARAEditorView())
 -         {
 -             g.setColour (Colours::white);
 -             g.setFont (15.0f);
 -             g.drawFittedText ("ARA host isn't detected. This plugin only supports ARA mode",
 -                               getLocalBounds(),
 -                               Justification::centred,
 -                               1);
 -         }
 -     }
 - 
 -     void resized() override
 -     {
 -         if (documentView != nullptr)
 -             documentView->setBounds (getLocalBounds());
 -     }
 - 
 - private:
 -     std::unique_ptr<Component> documentView;
 - 
 -     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARADemoPluginProcessorEditor)
 - };
 - 
 - class ARADemoPluginAudioProcessor  : public ARADemoPluginAudioProcessorImpl
 - {
 - public:
 -     bool hasEditor() const override               { return true; }
 -     AudioProcessorEditor* createEditor() override { return new ARADemoPluginProcessorEditor (*this); }
 - };
 
 
  |