|
- /*
- ==============================================================================
-
- 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
-
- #include <ARA_Library/Utilities/ARAPitchInterpretation.h>
- #include <ARA_Library/Utilities/ARATimelineConversion.h>
-
- //==============================================================================
- class ARADemoPluginAudioModification final : public ARAAudioModification
- {
- public:
- ARADemoPluginAudioModification (ARAAudioSource* audioSource,
- ARA::ARAAudioModificationHostRef hostRef,
- const ARAAudioModification* optionalModificationToClone)
- : ARAAudioModification (audioSource, hostRef, optionalModificationToClone)
- {
- if (optionalModificationToClone != nullptr)
- dimmed = static_cast<const ARADemoPluginAudioModification*> (optionalModificationToClone)->dimmed;
- }
-
- bool isDimmed() const { return dimmed; }
- void setDimmed (bool shouldDim) { dimmed = shouldDim; }
-
- private:
- bool dimmed = false;
- };
-
- //==============================================================================
- struct PreviewState
- {
- std::atomic<double> previewTime { 0.0 };
- std::atomic<ARAPlaybackRegion*> previewedRegion { nullptr };
- };
-
- class SharedTimeSliceThread final : public TimeSliceThread
- {
- public:
- SharedTimeSliceThread()
- : TimeSliceThread (String (JucePlugin_Name) + " ARA Sample Reading Thread")
- {
- startThread (Priority::high); // Above default priority so playback is fluent, but below realtime
- }
- };
-
- class AsyncConfigurationCallback final : 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;
- };
-
- static void crossfade (const float* sourceA,
- const float* sourceB,
- float aProportionAtStart,
- float aProportionAtFinish,
- float* destinationBuffer,
- int numSamples)
- {
- AudioBuffer<float> destination { &destinationBuffer, 1, numSamples };
- destination.copyFromWithRamp (0, 0, sourceA, numSamples, aProportionAtStart, aProportionAtFinish);
- destination.addFromWithRamp (0, 0, sourceB, numSamples, 1.0f - aProportionAtStart, 1.0f - aProportionAtFinish);
- }
-
- 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();
- return;
- }
-
- const auto numChannelsToCopy = std::min (inputBuffer->getNumChannels(), buffer.getNumChannels());
- const auto actualCrossfadeLengthSamples = std::min (loopRange.getLength() / 2, (int64) desiredCrossfadeLengthSamples);
-
- for (auto samplesCopied = 0; samplesCopied < buffer.getNumSamples();)
- {
- const auto [needsCrossfade, samplePosOfNextCrossfadeTransition] = [&]() -> std::pair<bool, int64>
- {
- if (const auto endOfFadeIn = loopRange.getStart() + actualCrossfadeLengthSamples; pos < endOfFadeIn)
- return { true, endOfFadeIn };
-
- return { false, loopRange.getEnd() - actualCrossfadeLengthSamples };
- }();
-
- const auto samplesToNextCrossfadeTransition = samplePosOfNextCrossfadeTransition - pos;
- const auto numSamplesToCopy = std::min (buffer.getNumSamples() - samplesCopied,
- (int) samplesToNextCrossfadeTransition);
-
- const auto getFadeInGainAtPos = [this, actualCrossfadeLengthSamples] (auto p)
- {
- return jmap ((float) p, (float) loopRange.getStart(), (float) loopRange.getStart() + (float) actualCrossfadeLengthSamples - 1.0f, 0.0f, 1.0f);
- };
-
- for (int i = 0; i < numChannelsToCopy; ++i)
- {
- if (needsCrossfade)
- {
- const auto overlapStart = loopRange.getEnd() - actualCrossfadeLengthSamples
- + (pos - loopRange.getStart());
-
- crossfade (inputBuffer->getReadPointer (i, (int) pos),
- inputBuffer->getReadPointer (i, (int) overlapStart),
- getFadeInGainAtPos (pos),
- getFadeInGainAtPos (pos + numSamplesToCopy),
- buffer.getWritePointer (i, samplesCopied),
- numSamplesToCopy);
- }
- else
- {
- buffer.copyFrom (i, samplesCopied, *inputBuffer, i, (int) pos, numSamplesToCopy);
- }
- }
-
- samplesCopied += numSamplesToCopy;
- pos += numSamplesToCopy;
-
- jassert (pos <= loopRange.getEnd() - actualCrossfadeLengthSamples);
-
- if (pos == loopRange.getEnd() - actualCrossfadeLengthSamples)
- pos = loopRange.getStart();
- }
- }
-
- private:
- static constexpr int desiredCrossfadeLengthSamples = 50;
-
- const AudioBuffer<float>* inputBuffer;
- Range<int64> loopRange;
- int64 pos;
- };
-
- //==============================================================================
- // Returns the modified sample range in the output buffer.
- inline std::optional<Range<int64>> readPlaybackRangeIntoBuffer (Range<double> playbackRange,
- const ARAPlaybackRegion* playbackRegion,
- AudioBuffer<float>& buffer,
- const std::function<AudioFormatReader* (ARAAudioSource*)>& getReader)
- {
- const auto rangeInAudioModificationTime = playbackRange - playbackRegion->getStartInPlaybackTime()
- + playbackRegion->getStartInAudioModificationTime();
-
- const auto audioModification = playbackRegion->getAudioModification<ARADemoPluginAudioModification>();
- const auto audioSource = audioModification->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());
-
- // With the output offset it can always be said of the output buffer, that the zeroth element
- // corresponds to beginning of the playbackRange.
- const auto outputOffset = std::max (-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 Range<int64>();
-
- auto* reader = getReader (audioSource);
-
- if (reader != nullptr && reader->read (&buffer, (int) outputOffset, (int) readLength, inputOffset, true, true))
- {
- if (audioModification->isDimmed())
- buffer.applyGain ((int) outputOffset, (int) readLength, 0.25f);
-
- return Range<int64>::withStartAndLength (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;
- };
-
- struct ProcessingLockInterface
- {
- virtual ~ProcessingLockInterface() = default;
- virtual ScopedTryReadLock getProcessingLock() = 0;
- };
-
- //==============================================================================
- class PlaybackRenderer final : public ARAPlaybackRenderer
- {
- public:
- PlaybackRenderer (ARA::PlugIn::DocumentController* dc, ProcessingLockInterface& lockInterfaceIn)
- : ARAPlaybackRenderer (dc), lockInterface (lockInterfaceIn) {}
-
- 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 lock = lockInterface.getProcessingLock();
-
- if (! lock.isLocked())
- return true;
-
- 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;
- }
-
- // Apply dim if enabled
- if (playbackRegion->getAudioModification<ARADemoPluginAudioModification>()->isDimmed())
- readBuffer.applyGain (startInBuffer, numSamplesToRead, 0.25f); // dim by about 12 dB
-
- // Mix output of all regions
- 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;
- }
-
- using ARAPlaybackRenderer::processBlock;
-
- private:
- //==============================================================================
- ProcessingLockInterface& lockInterface;
- SharedResourcePointer<SharedTimeSliceThread> sharedTimesliceThread;
- std::map<ARAAudioSource*, PossiblyBufferedReader> audioSourceReaders;
- bool useBufferedAudioSourceReader = true;
- int numChannels = 2;
- double sampleRate = 48000.0;
- int maximumSamplesPerBlock = 128;
- std::unique_ptr<AudioBuffer<float>> tempBuffer;
- };
-
- class EditorRenderer final : public ARAEditorRenderer,
- private ARARegionSequence::Listener
- {
- public:
- EditorRenderer (ARA::PlugIn::DocumentController* documentController,
- const PreviewState* previewStateIn,
- ProcessingLockInterface& lockInterfaceIn)
- : ARAEditorRenderer (documentController),
- lockInterface (lockInterfaceIn),
- previewState (previewStateIn)
- {
- 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 = static_cast<ARARegionSequence*> (rs);
- sequence->addListener (this);
- regionSequences.insert (sequence);
- asyncConfigCallback.startConfigure();
- }
-
- void willRemoveRegionSequence (ARA::PlugIn::RegionSequence* rs) noexcept override
- {
- auto* rsToRemove = static_cast<ARARegionSequence*> (rs);
- rsToRemove->removeListener (this);
- regionSequences.erase (rsToRemove);
- }
-
- 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);
-
- const auto lock = lockInterface.getProcessingLock();
-
- if (! lock.isLocked())
- return true;
-
- return asyncConfigCallback.withLock ([&] (bool locked)
- {
- if (! locked)
- return true;
-
- const auto fadeOutIfNecessary = [this, &buffer]
- {
- if (std::exchange (wasPreviewing, false))
- {
- previewLooper.writeInto (buffer);
- const auto fadeOutStart = std::max (0, buffer.getNumSamples() - 50);
- buffer.applyGainRamp (fadeOutStart, buffer.getNumSamples() - fadeOutStart, 1.0f, 0.0f);
- }
- };
-
- if (positionInfo.getIsPlaying())
- {
- fadeOutIfNecessary();
- 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();
- const auto previewDimmed = previewedRegion->getAudioModification<ARADemoPluginAudioModification>()
- ->isDimmed();
-
- if (! exactlyEqual (lastPreviewTime, previewTime)
- || ! exactlyEqual (lastPlaybackRegion, previewedRegion)
- || ! exactlyEqual (lastPreviewDimmed, previewDimmed))
- {
- 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;
- lastPreviewDimmed = previewDimmed;
- previewLooper = Looper (previewBuffer.get(), *rangeInOutput);
- }
- }
- else
- {
- previewLooper.writeInto (buffer);
-
- if (! std::exchange (wasPreviewing, true))
- {
- const auto fadeInLength = std::min (50, buffer.getNumSamples());
- buffer.applyGainRamp (0, fadeInLength, 0.0f, 1.0f);
- }
- }
- }
- }
- else
- {
- fadeOutIfNecessary();
- }
-
- return true;
- });
- }
-
- using ARAEditorRenderer::processBlock;
-
- 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;
- });
- }
-
- ProcessingLockInterface& lockInterface;
- const PreviewState* previewState = nullptr;
- AsyncConfigurationCallback asyncConfigCallback { [this] { configure(); } };
- double lastPreviewTime = 0.0;
- ARAPlaybackRegion* lastPlaybackRegion = nullptr;
- bool lastPreviewDimmed = false;
- bool wasPreviewing = false;
- std::unique_ptr<AudioBuffer<float>> previewBuffer;
- Looper previewLooper;
-
- double sampleRate = 48000.0;
- SharedResourcePointer<SharedTimeSliceThread> timeSliceThread;
- std::map<ARAAudioSource*, std::unique_ptr<BufferingAudioReader>> audioSourceReaders;
-
- std::set<ARARegionSequence*> regionSequences;
- };
-
- //==============================================================================
- class ARADemoPluginDocumentControllerSpecialisation final : public ARADocumentControllerSpecialisation,
- private ProcessingLockInterface
- {
- public:
- using ARADocumentControllerSpecialisation::ARADocumentControllerSpecialisation;
-
- PreviewState previewState;
-
- protected:
- void willBeginEditing (ARADocument*) override
- {
- processBlockLock.enterWrite();
- }
-
- void didEndEditing (ARADocument*) override
- {
- processBlockLock.exitWrite();
- }
-
- ARAAudioModification* doCreateAudioModification (ARAAudioSource* audioSource,
- ARA::ARAAudioModificationHostRef hostRef,
- const ARAAudioModification* optionalModificationToClone) noexcept override
- {
- return new ARADemoPluginAudioModification (audioSource,
- hostRef,
- static_cast<const ARADemoPluginAudioModification*> (optionalModificationToClone));
- }
-
- ARAPlaybackRenderer* doCreatePlaybackRenderer() noexcept override
- {
- return new PlaybackRenderer (getDocumentController(), *this);
- }
-
- EditorRenderer* doCreateEditorRenderer() noexcept override
- {
- return new EditorRenderer (getDocumentController(), &previewState, *this);
- }
-
- bool doRestoreObjectsFromStream (ARAInputStream& input,
- const ARARestoreObjectsFilter* filter) noexcept override
- {
- // Start reading data from the archive, starting with the number of audio modifications in the archive
- const auto numAudioModifications = input.readInt64();
-
- // Loop over stored audio modification data
- for (int64 i = 0; i < numAudioModifications; ++i)
- {
- const auto progressVal = (float) i / (float) numAudioModifications;
- getDocumentController()->getHostArchivingController()->notifyDocumentUnarchivingProgress (progressVal);
-
- // Read audio modification persistent ID and analysis result from archive
- const String persistentID = input.readString();
- const bool dimmed = input.readBool();
-
- // Find audio modification to restore the state to (drop state if not to be loaded)
- auto audioModification = filter->getAudioModificationToRestoreStateWithID<ARADemoPluginAudioModification> (persistentID.getCharPointer());
-
- if (audioModification == nullptr)
- continue;
-
- const bool dimChanged = (dimmed != audioModification->isDimmed());
- audioModification->setDimmed (dimmed);
-
- // If the dim state changed, send a sample content change notification without notifying the host
- if (dimChanged)
- {
- audioModification->notifyContentChanged (ARAContentUpdateScopes::samplesAreAffected(), false);
-
- for (auto playbackRegion : audioModification->getPlaybackRegions())
- playbackRegion->notifyContentChanged (ARAContentUpdateScopes::samplesAreAffected(), false);
- }
- }
-
- getDocumentController()->getHostArchivingController()->notifyDocumentUnarchivingProgress (1.0f);
-
- return ! input.failed();
- }
-
- bool doStoreObjectsToStream (ARAOutputStream& output, const ARAStoreObjectsFilter* filter) noexcept override
- {
- // This example implementation only deals with audio modification states
- const auto& audioModificationsToPersist { filter->getAudioModificationsToStore<ARADemoPluginAudioModification>() };
-
- const auto reportProgress = [archivingController = getDocumentController()->getHostArchivingController()] (float p)
- {
- archivingController->notifyDocumentArchivingProgress (p);
- };
-
- const ScopeGuard scope { [&reportProgress] { reportProgress (1.0f); } };
-
- // Write the number of audio modifications we are persisting
- const auto numAudioModifications = audioModificationsToPersist.size();
-
- if (! output.writeInt64 ((int64) numAudioModifications))
- return false;
-
- // For each audio modification to persist, persist its ID followed by whether it's dimmed
- for (size_t i = 0; i < numAudioModifications; ++i)
- {
- // Write persistent ID and dim state
- if (! output.writeString (audioModificationsToPersist[i]->getPersistentID()))
- return false;
-
- if (! output.writeBool (audioModificationsToPersist[i]->isDimmed()))
- return false;
-
- const auto progressVal = (float) i / (float) numAudioModifications;
- reportProgress (progressVal);
- }
-
- return true;
- }
-
- private:
- ScopedTryReadLock getProcessingLock() override
- {
- return ScopedTryReadLock { processBlockLock };
- }
-
- ReadWriteLock processBlockLock;
- };
-
- struct PlayHeadState
- {
- void update (const Optional<AudioPlayHead::PositionInfo>& info)
- {
- if (info.hasValue())
- {
- isPlaying.store (info->getIsPlaying(), std::memory_order_relaxed);
- timeInSeconds.store (info->getTimeInSeconds().orFallback (0), std::memory_order_relaxed);
- isLooping.store (info->getIsLooping(), std::memory_order_relaxed);
- const auto loopPoints = info->getLoopPoints();
-
- if (loopPoints.hasValue())
- {
- loopPpqStart = loopPoints->ppqStart;
- loopPpqEnd = loopPoints->ppqEnd;
- }
- }
- else
- {
- isPlaying.store (false, std::memory_order_relaxed);
- isLooping.store (false, std::memory_order_relaxed);
- }
- }
-
- std::atomic<bool> isPlaying { false },
- isLooping { false };
- std::atomic<double> timeInSeconds { 0.0 },
- loopPpqStart { 0.0 },
- loopPpqEnd { 0.0 };
- };
-
- //==============================================================================
- class ARADemoPluginAudioProcessorImpl : public AudioProcessor,
- public AudioProcessorARAExtension
- {
- public:
- //==============================================================================
- ARADemoPluginAudioProcessorImpl()
- : AudioProcessor (getBusesProperties())
- {}
-
- ~ARADemoPluginAudioProcessorImpl() override = default;
-
- //==============================================================================
- void prepareToPlay (double sampleRate, int samplesPerBlock) override
- {
- playHeadState.update (nullopt);
- prepareToPlayForARA (sampleRate, samplesPerBlock, getMainBusNumOutputChannels(), getProcessingPrecision());
- }
-
- void releaseResources() override
- {
- playHeadState.update (nullopt);
- 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;
-
- auto* audioPlayHead = getPlayHead();
- playHeadState.update (audioPlayHead->getPosition());
-
- if (! processBlockForARA (buffer, isRealtime(), audioPlayHead))
- processBlockBypassed (buffer, midiMessages);
- }
-
- using AudioProcessor::processBlock;
-
- //==============================================================================
- const String getName() const override { return "ARAPluginDemo"; }
- bool acceptsMidi() const override { return true; }
- bool producesMidi() const override { return true; }
-
- double getTailLengthSeconds() const override
- {
- double tail;
- if (getTailLengthSecondsForARA (tail))
- return tail;
-
- 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 {}
-
- PlayHeadState playHeadState;
-
- private:
- //==============================================================================
- static BusesProperties getBusesProperties()
- {
- return BusesProperties().withInput ("Input", AudioChannelSet::stereo(), true)
- .withOutput ("Output", AudioChannelSet::stereo(), true);
- }
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ARADemoPluginAudioProcessorImpl)
- };
-
- //==============================================================================
- class TimeToViewScaling
- {
- public:
- class Listener
- {
- public:
- virtual ~Listener() = default;
-
- virtual void zoomLevelChanged (double newPixelPerSecond) = 0;
- };
-
- void addListener (Listener* l) { listeners.add (l); }
- void removeListener (Listener* l) { listeners.remove (l); }
-
- TimeToViewScaling() = default;
-
- void zoom (double factor)
- {
- zoomLevelPixelPerSecond = jlimit (minimumZoom, minimumZoom * 32, zoomLevelPixelPerSecond * factor);
- setZoomLevel (zoomLevelPixelPerSecond);
- }
-
- void setZoomLevel (double pixelPerSecond)
- {
- zoomLevelPixelPerSecond = pixelPerSecond;
- listeners.call ([this] (Listener& l) { l.zoomLevelChanged (zoomLevelPixelPerSecond); });
- }
-
- int getXForTime (double time) const
- {
- return roundToInt (time * zoomLevelPixelPerSecond);
- }
-
- double getTimeForX (int x) const
- {
- return x / zoomLevelPixelPerSecond;
- }
-
- private:
- static constexpr auto minimumZoom = 10.0;
-
- double zoomLevelPixelPerSecond = minimumZoom * 4;
- ListenerList<Listener> listeners;
- };
-
- class RulersView final : public Component,
- public SettableTooltipClient,
- private Timer,
- private TimeToViewScaling::Listener,
- private ARAMusicalContext::Listener
- {
- public:
- class CycleMarkerComponent final : public Component
- {
- void paint (Graphics& g) override
- {
- g.setColour (Colours::yellow.darker (0.2f));
- const auto bounds = getLocalBounds().toFloat();
- g.drawRoundedRectangle (bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), 6.0f, 2.0f);
- }
- };
-
- RulersView (PlayHeadState& playHeadStateIn, TimeToViewScaling& timeToViewScalingIn, ARADocument& document)
- : playHeadState (playHeadStateIn), timeToViewScaling (timeToViewScalingIn), araDocument (document)
- {
- timeToViewScaling.addListener (this);
-
- addChildComponent (cycleMarker);
- cycleMarker.setInterceptsMouseClicks (false, false);
-
- setTooltip ("Double-click to start playback, click to stop playback or to reposition, drag horizontal range to set cycle.");
-
- startTimerHz (30);
- }
-
- ~RulersView() override
- {
- stopTimer();
-
- timeToViewScaling.removeListener (this);
- selectMusicalContext (nullptr);
- }
-
- void paint (Graphics& g) override
- {
- auto drawBounds = g.getClipBounds();
- const auto drawStartTime = timeToViewScaling.getTimeForX (drawBounds.getX());
- const auto drawEndTime = timeToViewScaling.getTimeForX (drawBounds.getRight());
-
- const auto bounds = getLocalBounds();
-
- g.setColour (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
- g.fillRect (bounds);
- g.setColour (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).contrasting());
- g.drawRect (bounds);
-
- const auto rulerHeight = bounds.getHeight() / 3;
- g.drawRect (drawBounds.getX(), rulerHeight, drawBounds.getRight(), rulerHeight);
- g.setFont (Font (12.0f));
-
- const int lightLineWidth = 1;
- const int heavyLineWidth = 3;
-
- if (selectedMusicalContext != nullptr)
- {
- const ARA::PlugIn::HostContentReader<ARA::kARAContentTypeTempoEntries> tempoReader (selectedMusicalContext);
- const ARA::TempoConverter<decltype (tempoReader)> tempoConverter (tempoReader);
-
- // chord ruler: one rect per chord, skipping empty "no chords"
- const auto chordBounds = drawBounds.removeFromTop (rulerHeight);
- const ARA::PlugIn::HostContentReader<ARA::kARAContentTypeSheetChords> chordsReader (selectedMusicalContext);
-
- if (tempoReader && chordsReader)
- {
- const ARA::ChordInterpreter interpreter (true);
- for (auto itChord = chordsReader.begin(); itChord != chordsReader.end(); ++itChord)
- {
- if (interpreter.isNoChord (*itChord))
- continue;
-
- const auto chordStartTime = (itChord == chordsReader.begin()) ? 0 : tempoConverter.getTimeForQuarter (itChord->position);
-
- if (chordStartTime >= drawEndTime)
- break;
-
- auto chordRect = chordBounds;
- chordRect.setLeft (timeToViewScaling.getXForTime (chordStartTime));
-
- if (std::next (itChord) != chordsReader.end())
- {
- const auto nextChordStartTime = tempoConverter.getTimeForQuarter (std::next (itChord)->position);
-
- if (nextChordStartTime < drawStartTime)
- continue;
-
- chordRect.setRight (timeToViewScaling.getXForTime (nextChordStartTime));
- }
-
- g.drawRect (chordRect);
- g.drawText (convertARAString (interpreter.getNameForChord (*itChord).c_str()),
- chordRect.withTrimmedLeft (2),
- Justification::centredLeft);
- }
- }
-
- // beat ruler: evaluates tempo and bar signatures to draw a line for each beat
- const ARA::PlugIn::HostContentReader<ARA::kARAContentTypeBarSignatures> barSignaturesReader (selectedMusicalContext);
-
- if (barSignaturesReader)
- {
- const ARA::BarSignaturesConverter<decltype (barSignaturesReader)> barSignaturesConverter (barSignaturesReader);
-
- const double beatStart = barSignaturesConverter.getBeatForQuarter (tempoConverter.getQuarterForTime (drawStartTime));
- const double beatEnd = barSignaturesConverter.getBeatForQuarter (tempoConverter.getQuarterForTime (drawEndTime));
- const int endBeat = roundToInt (std::floor (beatEnd));
- RectangleList<int> rects;
-
- for (int beat = roundToInt (std::ceil (beatStart)); beat <= endBeat; ++beat)
- {
- const auto quarterPos = barSignaturesConverter.getQuarterForBeat (beat);
- const int x = timeToViewScaling.getXForTime (tempoConverter.getTimeForQuarter (quarterPos));
- const auto barSignature = barSignaturesConverter.getBarSignatureForQuarter (quarterPos);
- const int lineWidth = (approximatelyEqual (quarterPos, barSignature.position)) ? heavyLineWidth : lightLineWidth;
- const int beatsSinceBarStart = roundToInt (barSignaturesConverter.getBeatDistanceFromBarStartForQuarter (quarterPos));
- const int lineHeight = (beatsSinceBarStart == 0) ? rulerHeight : rulerHeight / 2;
- rects.addWithoutMerging (Rectangle<int> (x - lineWidth / 2, 2 * rulerHeight - lineHeight, lineWidth, lineHeight));
- }
-
- g.fillRectList (rects);
- }
- }
-
- // time ruler: one tick for each second
- {
- RectangleList<int> rects;
-
- for (auto time = std::floor (drawStartTime); time <= drawEndTime; time += 1.0)
- {
- const int lineWidth = (std::fmod (time, 60.0) <= 0.001) ? heavyLineWidth : lightLineWidth;
- const int lineHeight = (std::fmod (time, 10.0) <= 0.001) ? rulerHeight : rulerHeight / 2;
- rects.addWithoutMerging (Rectangle<int> (timeToViewScaling.getXForTime (time) - lineWidth / 2,
- bounds.getHeight() - lineHeight,
- lineWidth,
- lineHeight));
- }
-
- g.fillRectList (rects);
- }
- }
-
- void mouseDrag (const MouseEvent& m) override
- {
- isDraggingCycle = true;
-
- auto cycleRect = getBounds();
- cycleRect.setLeft (jmin (m.getMouseDownX(), m.x));
- cycleRect.setRight (jmax (m.getMouseDownX(), m.x));
- cycleMarker.setBounds (cycleRect);
- }
-
- void mouseUp (const MouseEvent& m) override
- {
- auto playbackController = araDocument.getDocumentController()->getHostPlaybackController();
-
- if (playbackController != nullptr)
- {
- const auto startTime = timeToViewScaling.getTimeForX (jmin (m.getMouseDownX(), m.x));
- const auto endTime = timeToViewScaling.getTimeForX (jmax (m.getMouseDownX(), m.x));
-
- if (playHeadState.isPlaying.load (std::memory_order_relaxed))
- playbackController->requestStopPlayback();
- else
- playbackController->requestSetPlaybackPosition (startTime);
-
- if (isDraggingCycle)
- playbackController->requestSetCycleRange (startTime, endTime - startTime);
- }
-
- isDraggingCycle = false;
- }
-
- void mouseDoubleClick (const MouseEvent&) override
- {
- if (auto* playbackController = araDocument.getDocumentController()->getHostPlaybackController())
- {
- if (! playHeadState.isPlaying.load (std::memory_order_relaxed))
- playbackController->requestStartPlayback();
- }
- }
-
- void selectMusicalContext (ARAMusicalContext* newSelectedMusicalContext)
- {
- if (auto* oldSelection = std::exchange (selectedMusicalContext, newSelectedMusicalContext);
- oldSelection != selectedMusicalContext)
- {
- if (oldSelection != nullptr)
- oldSelection->removeListener (this);
-
- if (selectedMusicalContext != nullptr)
- selectedMusicalContext->addListener (this);
-
- repaint();
- }
- }
-
- void zoomLevelChanged (double) override
- {
- repaint();
- }
-
- void doUpdateMusicalContextContent (ARAMusicalContext*, ARAContentUpdateScopes) override
- {
- repaint();
- }
-
- private:
- void updateCyclePosition()
- {
- if (selectedMusicalContext != nullptr)
- {
- const ARA::PlugIn::HostContentReader<ARA::kARAContentTypeTempoEntries> tempoReader (selectedMusicalContext);
- const ARA::TempoConverter<decltype (tempoReader)> tempoConverter (tempoReader);
-
- const auto loopStartTime = tempoConverter.getTimeForQuarter (playHeadState.loopPpqStart.load (std::memory_order_relaxed));
- const auto loopEndTime = tempoConverter.getTimeForQuarter (playHeadState.loopPpqEnd.load (std::memory_order_relaxed));
-
- auto cycleRect = getBounds();
- cycleRect.setLeft (timeToViewScaling.getXForTime (loopStartTime));
- cycleRect.setRight (timeToViewScaling.getXForTime (loopEndTime));
- cycleMarker.setVisible (true);
- cycleMarker.setBounds (cycleRect);
- }
- else
- {
- cycleMarker.setVisible (false);
- }
- }
-
- void timerCallback() override
- {
- if (! isDraggingCycle)
- updateCyclePosition();
- }
-
- private:
- PlayHeadState& playHeadState;
- TimeToViewScaling& timeToViewScaling;
- ARADocument& araDocument;
- ARAMusicalContext* selectedMusicalContext = nullptr;
- CycleMarkerComponent cycleMarker;
- bool isDraggingCycle = false;
- };
-
- class RulersHeader final : public Component
- {
- public:
- RulersHeader()
- {
- chordsLabel.setText ("Chords", NotificationType::dontSendNotification);
- addAndMakeVisible (chordsLabel);
-
- barsLabel.setText ("Bars", NotificationType::dontSendNotification);
- addAndMakeVisible (barsLabel);
-
- timeLabel.setText ("Time", NotificationType::dontSendNotification);
- addAndMakeVisible (timeLabel);
- }
-
- void resized() override
- {
- auto bounds = getLocalBounds();
- const auto rulerHeight = bounds.getHeight() / 3;
-
- for (auto* label : { &chordsLabel, &barsLabel, &timeLabel })
- label->setBounds (bounds.removeFromTop (rulerHeight));
- }
-
- void paint (Graphics& g) override
- {
- auto bounds = getLocalBounds();
- const auto rulerHeight = bounds.getHeight() / 3;
- g.setColour (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
- g.fillRect (bounds);
- g.setColour (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).contrasting());
- g.drawRect (bounds);
- bounds.removeFromTop (rulerHeight);
- g.drawRect (bounds.removeFromTop (rulerHeight));
- }
-
- private:
- Label chordsLabel, barsLabel, timeLabel;
- };
-
- //==============================================================================
- struct WaveformCache final : 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 final : public Component,
- public ChangeListener,
- public SettableTooltipClient,
- private ARAAudioSource::Listener,
- private ARAPlaybackRegion::Listener,
- private ARAEditorView::Listener
- {
- public:
- PlaybackRegionView (ARAEditorView& editorView, ARAPlaybackRegion& region, WaveformCache& cache)
- : araEditorView (editorView), playbackRegion (region), waveformCache (cache), previewRegionOverlay (*this)
- {
- auto* audioSource = playbackRegion.getAudioModification()->getAudioSource();
-
- waveformCache.getOrCreateThumbnail (audioSource).addChangeListener (this);
-
- audioSource->addListener (this);
- playbackRegion.addListener (this);
- araEditorView.addListener (this);
- addAndMakeVisible (previewRegionOverlay);
-
- setTooltip ("Double-click to toggle dim state of the region, click and hold to prelisten region near click.");
- }
-
- ~PlaybackRegionView() override
- {
- auto* audioSource = playbackRegion.getAudioModification()->getAudioSource();
-
- audioSource->removeListener (this);
- playbackRegion.removeListener (this);
- araEditorView.removeListener (this);
-
- waveformCache.getOrCreateThumbnail (audioSource).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);
- previewRegionOverlay.update();
- }
-
- void mouseUp (const MouseEvent&) override
- {
- auto& previewState = ARADocumentControllerSpecialisation::getSpecialisedDocumentController<ARADemoPluginDocumentControllerSpecialisation> (playbackRegion.getDocumentController())->previewState;
- previewState.previewTime.store (0.0);
- previewState.previewedRegion.store (nullptr);
- previewRegionOverlay.update();
- }
-
- void mouseDoubleClick (const MouseEvent&) override
- {
- // Set the dim flag on our region's audio modification when double-clicked
- auto audioModification = playbackRegion.getAudioModification<ARADemoPluginAudioModification>();
- audioModification->setDimmed (! audioModification->isDimmed());
-
- // Send a content change notification for the modification and all associated playback regions
- audioModification->notifyContentChanged (ARAContentUpdateScopes::samplesAreAffected(), true);
- for (auto region : audioModification->getPlaybackRegions())
- region->notifyContentChanged (ARAContentUpdateScopes::samplesAreAffected(), true);
- }
-
- void changeListenerCallback (ChangeBroadcaster*) override
- {
- repaint();
- }
-
- void didEnableAudioSourceSamplesAccess (ARAAudioSource*, bool) override
- {
- repaint();
- }
-
- void willUpdatePlaybackRegionProperties (ARAPlaybackRegion*,
- ARAPlaybackRegion::PropertiesPtr newProperties) override
- {
- if (playbackRegion.getName() != newProperties->name
- || playbackRegion.getColor() != newProperties->color)
- {
- repaint();
- }
- }
-
- void didUpdatePlaybackRegionContent (ARAPlaybackRegion*, ARAContentUpdateScopes) override
- {
- repaint();
- }
-
- void onNewSelection (const ARAViewSelection& viewSelection) override
- {
- const auto& selectedPlaybackRegions = viewSelection.getPlaybackRegions();
- const bool selected = std::find (selectedPlaybackRegions.begin(), selectedPlaybackRegions.end(), &playbackRegion) != selectedPlaybackRegions.end();
- if (selected != isSelected)
- {
- isSelected = selected;
- repaint();
- }
- }
-
- void paint (Graphics& g) override
- {
- g.fillAll (convertOptionalARAColour (playbackRegion.getEffectiveColor(), Colours::black));
-
- const auto* audioModification = playbackRegion.getAudioModification<ARADemoPluginAudioModification>();
- g.setColour (audioModification->isDimmed() ? Colours::darkgrey.darker() : Colours::darkgrey.brighter());
-
- if (audioModification->getAudioSource()->isSampleAccessEnabled())
- {
- auto& thumbnail = waveformCache.getOrCreateThumbnail (playbackRegion.getAudioModification()->getAudioSource());
- thumbnail.drawChannels (g,
- getLocalBounds(),
- playbackRegion.getStartInAudioModificationTime(),
- playbackRegion.getEndInAudioModificationTime(),
- 1.0f);
- }
- else
- {
- g.setFont (Font (12.0f));
- g.drawText ("Audio Access Disabled", getLocalBounds(), Justification::centred);
- }
-
- g.setColour (Colours::white.withMultipliedAlpha (0.9f));
- g.setFont (Font (12.0f));
- g.drawText (convertOptionalARAString (playbackRegion.getEffectiveName()),
- getLocalBounds(),
- Justification::topLeft);
-
- if (audioModification->isDimmed())
- g.drawText ("DIMMED", getLocalBounds(), Justification::bottomLeft);
-
- g.setColour (isSelected ? Colours::white : Colours::black);
- g.drawRect (getLocalBounds());
- }
-
- void resized() override
- {
- repaint();
- }
-
- private:
- class PreviewRegionOverlay final : public Component
- {
- static constexpr auto previewLength = 0.5;
-
- public:
- PreviewRegionOverlay (PlaybackRegionView& ownerIn) : owner (ownerIn)
- {
- }
-
- void update()
- {
- const auto& previewState = owner.getDocumentController()->previewState;
-
- if (previewState.previewedRegion.load() == &owner.playbackRegion)
- {
- const auto previewStartTime = previewState.previewTime.load() - owner.playbackRegion.getStartInPlaybackTime();
- const auto pixelPerSecond = owner.getWidth() / owner.playbackRegion.getDurationInPlaybackTime();
-
- setBounds (roundToInt ((previewStartTime - previewLength / 2) * pixelPerSecond),
- 0,
- roundToInt (previewLength * pixelPerSecond),
- owner.getHeight());
-
- setVisible (true);
- }
- else
- {
- setVisible (false);
- }
-
- repaint();
- }
-
- void paint (Graphics& g) override
- {
- g.setColour (Colours::yellow.withAlpha (0.5f));
- g.fillRect (getLocalBounds());
- }
-
- private:
- PlaybackRegionView& owner;
- };
-
- ARADemoPluginDocumentControllerSpecialisation* getDocumentController() const
- {
- return ARADocumentControllerSpecialisation::getSpecialisedDocumentController<ARADemoPluginDocumentControllerSpecialisation> (playbackRegion.getDocumentController());
- }
-
- ARAEditorView& araEditorView;
- ARAPlaybackRegion& playbackRegion;
- WaveformCache& waveformCache;
- PreviewRegionOverlay previewRegionOverlay;
- bool isSelected = false;
- };
-
- class RegionSequenceView final : public Component,
- public ChangeBroadcaster,
- private TimeToViewScaling::Listener,
- private ARARegionSequence::Listener,
- private ARAPlaybackRegion::Listener
- {
- public:
- RegionSequenceView (ARAEditorView& editorView, TimeToViewScaling& scaling, ARARegionSequence& rs, WaveformCache& cache)
- : araEditorView (editorView), timeToViewScaling (scaling), regionSequence (rs), waveformCache (cache)
- {
- regionSequence.addListener (this);
-
- for (auto* playbackRegion : regionSequence.getPlaybackRegions())
- createAndAddPlaybackRegionView (playbackRegion);
-
- updatePlaybackDuration();
-
- timeToViewScaling.addListener (this);
- }
-
- ~RegionSequenceView() override
- {
- timeToViewScaling.removeListener (this);
-
- regionSequence.removeListener (this);
-
- for (const auto& it : playbackRegionViews)
- it.first->removeListener (this);
- }
-
- //==============================================================================
- // ARA Document change callback overrides
- void willUpdateRegionSequenceProperties (ARARegionSequence*,
- ARARegionSequence::PropertiesPtr newProperties) override
- {
- if (regionSequence.getColor() != newProperties->color)
- {
- for (auto& pbr : playbackRegionViews)
- pbr.second->repaint();
- }
- }
-
- 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 didUpdatePlaybackRegionProperties (ARAPlaybackRegion*) override
- {
- updatePlaybackDuration();
- }
-
- void zoomLevelChanged (double) override
- {
- resized();
- }
-
- void resized() override
- {
- for (auto& pbr : playbackRegionViews)
- {
- const auto playbackRegion = pbr.first;
- pbr.second->setBounds (
- getLocalBounds()
- .withTrimmedLeft (timeToViewScaling.getXForTime (playbackRegion->getStartInPlaybackTime()))
- .withWidth (timeToViewScaling.getXForTime (playbackRegion->getDurationInPlaybackTime())));
- }
- }
-
- auto getPlaybackDuration() const noexcept
- {
- return playbackDuration;
- }
-
- private:
- void createAndAddPlaybackRegionView (ARAPlaybackRegion* playbackRegion)
- {
- playbackRegionViews[playbackRegion] = std::make_unique<PlaybackRegionView> (araEditorView,
- *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();
- }
-
- ARAEditorView& araEditorView;
- TimeToViewScaling& timeToViewScaling;
- ARARegionSequence& regionSequence;
- WaveformCache& waveformCache;
- std::unordered_map<ARAPlaybackRegion*, std::unique_ptr<PlaybackRegionView>> playbackRegionViews;
- double playbackDuration = 0.0;
- };
-
- class ZoomControls final : 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 PlayheadPositionLabel final : public Label,
- private Timer
- {
- public:
- PlayheadPositionLabel (PlayHeadState& playHeadStateIn)
- : playHeadState (playHeadStateIn)
- {
- startTimerHz (30);
- }
-
- ~PlayheadPositionLabel() override
- {
- stopTimer();
- }
-
- void selectMusicalContext (ARAMusicalContext* newSelectedMusicalContext)
- {
- selectedMusicalContext = newSelectedMusicalContext;
- }
-
- private:
- void timerCallback() override
- {
- const auto timePosition = playHeadState.timeInSeconds.load (std::memory_order_relaxed);
-
- auto text = timeToTimecodeString (timePosition);
-
- if (playHeadState.isPlaying.load (std::memory_order_relaxed))
- text += " (playing)";
- else
- text += " (stopped)";
-
- if (selectedMusicalContext != nullptr)
- {
- const ARA::PlugIn::HostContentReader<ARA::kARAContentTypeTempoEntries> tempoReader (selectedMusicalContext);
- const ARA::PlugIn::HostContentReader<ARA::kARAContentTypeBarSignatures> barSignaturesReader (selectedMusicalContext);
-
- if (tempoReader && barSignaturesReader)
- {
- const ARA::TempoConverter<decltype (tempoReader)> tempoConverter (tempoReader);
- const ARA::BarSignaturesConverter<decltype (barSignaturesReader)> barSignaturesConverter (barSignaturesReader);
- const auto quarterPosition = tempoConverter.getQuarterForTime (timePosition);
- const auto barIndex = barSignaturesConverter.getBarIndexForQuarter (quarterPosition);
- const auto beatDistance = barSignaturesConverter.getBeatDistanceFromBarStartForQuarter (quarterPosition);
- const auto quartersPerBeat = 4.0 / (double) barSignaturesConverter.getBarSignatureForQuarter (quarterPosition).denominator;
- const auto beatIndex = (int) beatDistance;
- const auto tickIndex = juce::roundToInt ((beatDistance - beatIndex) * quartersPerBeat * 960.0);
-
- text += newLine;
- text += String::formatted ("bar %d | beat %d | tick %03d", (barIndex >= 0) ? barIndex + 1 : barIndex, beatIndex + 1, tickIndex + 1);
- text += " - ";
-
- const ARA::PlugIn::HostContentReader<ARA::kARAContentTypeSheetChords> chordsReader (selectedMusicalContext);
-
- if (chordsReader && chordsReader.getEventCount() > 0)
- {
- const auto begin = chordsReader.begin();
- const auto end = chordsReader.end();
- auto it = begin;
-
- while (it != end && it->position <= quarterPosition)
- ++it;
-
- if (it != begin)
- --it;
-
- const ARA::ChordInterpreter interpreter (true);
- text += "chord ";
- text += String (interpreter.getNameForChord (*it));
- }
- else
- {
- text += "(no chords provided)";
- }
- }
- }
-
- setText (text, NotificationType::dontSendNotification);
- }
-
- // Copied from AudioPluginDemo.h: quick-and-dirty function to format a timecode string
- static String timeToTimecodeString (double seconds)
- {
- auto millisecs = roundToInt (seconds * 1000.0);
- auto absMillisecs = std::abs (millisecs);
-
- return String::formatted ("%02d:%02d:%02d.%03d",
- millisecs / 3600000,
- (absMillisecs / 60000) % 60,
- (absMillisecs / 1000) % 60,
- absMillisecs % 1000);
- }
-
- PlayHeadState& playHeadState;
- ARAMusicalContext* selectedMusicalContext = nullptr;
- };
-
- class TrackHeader final : public Component,
- private ARARegionSequence::Listener,
- private ARAEditorView::Listener
- {
- public:
- TrackHeader (ARAEditorView& editorView, ARARegionSequence& regionSequenceIn)
- : araEditorView (editorView), regionSequence (regionSequenceIn)
- {
- updateTrackName (regionSequence.getName());
- onNewSelection (araEditorView.getViewSelection());
-
- addAndMakeVisible (trackNameLabel);
-
- regionSequence.addListener (this);
- araEditorView.addListener (this);
- }
-
- ~TrackHeader() override
- {
- araEditorView.removeListener (this);
- regionSequence.removeListener (this);
- }
-
- void willUpdateRegionSequenceProperties (ARARegionSequence*, ARARegionSequence::PropertiesPtr newProperties) override
- {
- if (regionSequence.getName() != newProperties->name)
- updateTrackName (newProperties->name);
- if (regionSequence.getColor() != newProperties->color)
- repaint();
- }
-
- void resized() override
- {
- trackNameLabel.setBounds (getLocalBounds().reduced (2));
- }
-
- void paint (Graphics& g) override
- {
- const auto backgroundColour = getLookAndFeel().findColour (ResizableWindow::backgroundColourId);
- g.setColour (isSelected ? backgroundColour.brighter() : backgroundColour);
- g.fillRoundedRectangle (getLocalBounds().reduced (2).toFloat(), 6.0f);
- g.setColour (backgroundColour.contrasting());
- g.drawRoundedRectangle (getLocalBounds().reduced (2).toFloat(), 6.0f, 1.0f);
-
- if (auto colour = regionSequence.getColor())
- {
- g.setColour (convertARAColour (colour));
- g.fillRect (getLocalBounds().removeFromTop (16).reduced (6));
- g.fillRect (getLocalBounds().removeFromBottom (16).reduced (6));
- }
- }
-
- void onNewSelection (const ARAViewSelection& viewSelection) override
- {
- const auto& selectedRegionSequences = viewSelection.getRegionSequences();
- const bool selected = std::find (selectedRegionSequences.begin(), selectedRegionSequences.end(), ®ionSequence) != selectedRegionSequences.end();
-
- if (selected != isSelected)
- {
- isSelected = selected;
- repaint();
- }
- }
-
- private:
- void updateTrackName (ARA::ARAUtf8String optionalName)
- {
- trackNameLabel.setText (optionalName ? optionalName : "No track name",
- NotificationType::dontSendNotification);
- }
-
- ARAEditorView& araEditorView;
- ARARegionSequence& regionSequence;
- Label trackNameLabel;
- bool isSelected = false;
- };
-
- constexpr auto trackHeight = 60;
-
- class VerticalLayoutViewportContent final : public Component
- {
- public:
- void resized() override
- {
- auto bounds = getLocalBounds();
-
- for (auto* component : getChildren())
- {
- component->setBounds (bounds.removeFromTop (trackHeight));
- component->resized();
- }
- }
- };
-
- class VerticalLayoutViewport final : 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 final : public Component,
- private Timer,
- private TimeToViewScaling::Listener
- {
- public:
- class PlayheadMarkerComponent final : public Component
- {
- void paint (Graphics& g) override { g.fillAll (Colours::yellow.darker (0.2f)); }
- };
-
- OverlayComponent (PlayHeadState& playHeadStateIn, TimeToViewScaling& timeToViewScalingIn)
- : playHeadState (playHeadStateIn), timeToViewScaling (timeToViewScalingIn)
- {
- addChildComponent (playheadMarker);
- setInterceptsMouseClicks (false, false);
- startTimerHz (30);
-
- timeToViewScaling.addListener (this);
- }
-
- ~OverlayComponent() override
- {
- timeToViewScaling.removeListener (this);
-
- stopTimer();
- }
-
- void resized() override
- {
- updatePlayHeadPosition();
- }
-
- void setHorizontalOffset (int offset)
- {
- horizontalOffset = offset;
- }
-
- void setSelectedTimeRange (std::optional<ARA::ARAContentTimeRange> timeRange)
- {
- selectedTimeRange = timeRange;
- repaint();
- }
-
- void zoomLevelChanged (double) override
- {
- updatePlayHeadPosition();
- repaint();
- }
-
- void paint (Graphics& g) override
- {
- if (selectedTimeRange)
- {
- auto bounds = getLocalBounds();
- bounds.setLeft (timeToViewScaling.getXForTime (selectedTimeRange->start));
- bounds.setRight (timeToViewScaling.getXForTime (selectedTimeRange->start + selectedTimeRange->duration));
- g.setColour (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).brighter().withAlpha (0.3f));
- g.fillRect (bounds);
- g.setColour (Colours::whitesmoke.withAlpha (0.5f));
- g.drawRect (bounds);
- }
- }
-
- private:
- void updatePlayHeadPosition()
- {
- if (playHeadState.isPlaying.load (std::memory_order_relaxed))
- {
- const auto markerX = timeToViewScaling.getXForTime (playHeadState.timeInSeconds.load (std::memory_order_relaxed));
- 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
- {
- updatePlayHeadPosition();
- }
-
- static constexpr double markerWidth = 2.0;
-
- PlayHeadState& playHeadState;
- TimeToViewScaling& timeToViewScaling;
- int horizontalOffset = 0;
- std::optional<ARA::ARAContentTimeRange> selectedTimeRange;
- PlayheadMarkerComponent playheadMarker;
- };
-
- class DocumentView final : public Component,
- public ChangeListener,
- public ARAMusicalContext::Listener,
- private ARADocument::Listener,
- private ARAEditorView::Listener
- {
- public:
- DocumentView (ARAEditorView& editorView, PlayHeadState& playHeadState)
- : araEditorView (editorView),
- araDocument (*editorView.getDocumentController()->getDocument<ARADocument>()),
- rulersView (playHeadState, timeToViewScaling, araDocument),
- overlay (playHeadState, timeToViewScaling),
- playheadPositionLabel (playHeadState)
- {
- if (araDocument.getMusicalContexts().size() > 0)
- selectMusicalContext (araDocument.getMusicalContexts().front());
-
- addAndMakeVisible (rulersHeader);
-
- viewport.content.addAndMakeVisible (rulersView);
-
- viewport.onVisibleAreaChanged = [this] (const auto& r)
- {
- viewportHeightOffset = r.getY();
- overlay.setHorizontalOffset (r.getX());
- resized();
- };
-
- addAndMakeVisible (viewport);
- addAndMakeVisible (overlay);
- addAndMakeVisible (playheadPositionLabel);
-
- zoomControls.setZoomInCallback ([this] { zoom (2.0); });
- zoomControls.setZoomOutCallback ([this] { zoom (0.5); });
- addAndMakeVisible (zoomControls);
-
- invalidateRegionSequenceViews();
-
- araDocument.addListener (this);
- araEditorView.addListener (this);
- }
-
- ~DocumentView() override
- {
- araEditorView.removeListener (this);
- araDocument.removeListener (this);
- selectMusicalContext (nullptr);
- }
-
- //==============================================================================
- // ARADocument::Listener overrides
- void didAddMusicalContextToDocument (ARADocument*, ARAMusicalContext* musicalContext) override
- {
- if (selectedMusicalContext == nullptr)
- selectMusicalContext (musicalContext);
- }
-
- void willDestroyMusicalContext (ARAMusicalContext* musicalContext) override
- {
- if (selectedMusicalContext == musicalContext)
- selectMusicalContext (nullptr);
- }
-
- 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 ARAViewSelection& viewSelection) override
- {
- auto getNewSelectedMusicalContext = [&viewSelection]() -> ARAMusicalContext*
- {
- if (! viewSelection.getRegionSequences().empty())
- return viewSelection.getRegionSequences<ARARegionSequence>().front()->getMusicalContext();
- else if (! viewSelection.getPlaybackRegions().empty())
- return viewSelection.getPlaybackRegions<ARAPlaybackRegion>().front()->getRegionSequence()->getMusicalContext();
-
- return nullptr;
- };
-
- if (auto* newSelectedMusicalContext = getNewSelectedMusicalContext())
- if (newSelectedMusicalContext != selectedMusicalContext)
- selectMusicalContext (newSelectedMusicalContext);
-
- if (const auto timeRange = viewSelection.getTimeRange())
- overlay.setSelectedTimeRange (*timeRange);
- else
- overlay.setSelectedTimeRange (std::nullopt);
- }
-
- void onHideRegionSequences (const std::vector<ARARegionSequence*>& regionSequences) override
- {
- hiddenRegionSequences = regionSequences;
- invalidateRegionSequenceViews();
- }
-
- //==============================================================================
- void paint (Graphics& g) override
- {
- g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).darker());
- }
-
- void resized() override
- {
- auto bounds = getLocalBounds();
-
- FlexBox fb;
- fb.justifyContent = FlexBox::JustifyContent::spaceBetween;
- fb.items.add (FlexItem (playheadPositionLabel).withWidth (450.0f).withMinWidth (250.0f));
- fb.items.add (FlexItem (zoomControls).withMinWidth (80.0f));
- fb.performLayout (bounds.removeFromBottom (40));
-
- auto headerBounds = bounds.removeFromLeft (headerWidth);
- rulersHeader.setBounds (headerBounds.removeFromTop (trackHeight));
- layOutVertically (headerBounds, trackHeaders, viewportHeightOffset);
-
- viewport.setBounds (bounds);
- overlay.setBounds (bounds.reduced (1));
-
- const auto width = jmax (timeToViewScaling.getXForTime (timelineLength), viewport.getWidth());
- const auto height = (int) (regionSequenceViews.size() + 1) * trackHeight;
- viewport.content.setSize (width, height);
- viewport.content.resized();
- }
-
- //==============================================================================
- 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 selectMusicalContext (ARAMusicalContext* newSelectedMusicalContext)
- {
- if (auto oldContext = std::exchange (selectedMusicalContext, newSelectedMusicalContext);
- oldContext != selectedMusicalContext)
- {
- if (oldContext != nullptr)
- oldContext->removeListener (this);
-
- if (selectedMusicalContext != nullptr)
- selectedMusicalContext->addListener (this);
-
- rulersView.selectMusicalContext (selectedMusicalContext);
- playheadPositionLabel.selectMusicalContext (selectedMusicalContext);
- }
- }
-
- void zoom (double factor)
- {
- timeToViewScaling.zoom (factor);
- update();
- }
-
- 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());
-
- 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> (araEditorView, timeToViewScaling, *regionSequence, waveformCache));
-
- regionSequenceView.addChangeListener (this);
- viewport.content.addAndMakeVisible (regionSequenceView);
-
- auto& trackHeader = insertIntoMap (trackHeaders,
- RegionSequenceViewKey { regionSequence },
- std::make_unique<TrackHeader> (araEditorView, *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())
- if (std::find (hiddenRegionSequences.begin(), hiddenRegionSequences.end(), regionSequence) == hiddenRegionSequences.end())
- addTrackViews (regionSequence);
-
- update();
-
- regionSequenceViewsAreValid = true;
- }
- }
-
- ARAEditorView& araEditorView;
- ARADocument& araDocument;
-
- bool regionSequenceViewsAreValid = false;
-
- TimeToViewScaling timeToViewScaling;
- double timelineLength = 0.0;
-
- ARAMusicalContext* selectedMusicalContext = nullptr;
-
- std::vector<ARARegionSequence*> hiddenRegionSequences;
-
- WaveformCache waveformCache;
- std::map<RegionSequenceViewKey, std::unique_ptr<TrackHeader>> trackHeaders;
- std::map<RegionSequenceViewKey, std::unique_ptr<RegionSequenceView>> regionSequenceViews;
- RulersHeader rulersHeader;
- RulersView rulersView;
- VerticalLayoutViewport viewport;
- OverlayComponent overlay;
- ZoomControls zoomControls;
- PlayheadPositionLabel playheadPositionLabel;
- TooltipWindow tooltip;
-
- int viewportHeightOffset = 0;
- };
-
-
- class ARADemoPluginProcessorEditor final : public AudioProcessorEditor,
- public AudioProcessorEditorARAExtension
- {
- public:
- explicit ARADemoPluginProcessorEditor (ARADemoPluginAudioProcessorImpl& p)
- : AudioProcessorEditor (&p),
- AudioProcessorEditorARAExtension (&p)
- {
- if (auto* editorView = getARAEditorView())
- documentView = std::make_unique<DocumentView> (*editorView, p.playHeadState);
-
- addAndMakeVisible (documentView.get());
-
- // ARA requires that plugin editors are resizable to support tight integration
- // into the host UI
- setResizable (true, false);
- setSize (800, 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 final : public ARADemoPluginAudioProcessorImpl
- {
- public:
- bool hasEditor() const override { return true; }
- AudioProcessorEditor* createEditor() override { return new ARADemoPluginProcessorEditor (*this); }
- };
|