From 23445ebd92a640ed23269f3df4a0df449256b465 Mon Sep 17 00:00:00 2001 From: attila Date: Thu, 10 Nov 2022 14:16:09 +0100 Subject: [PATCH] ARAPluginDemo: Add more features to demo host integration --- examples/Plugins/ARAPluginDemo.h | 1119 ++++++++++++++--- .../ARA/juce_ARAPlugInInstanceRoles.h | 4 +- 2 files changed, 963 insertions(+), 160 deletions(-) diff --git a/examples/Plugins/ARAPluginDemo.h b/examples/Plugins/ARAPluginDemo.h index 299c7689f2..89f7d1bdb3 100644 --- a/examples/Plugins/ARAPluginDemo.h +++ b/examples/Plugins/ARAPluginDemo.h @@ -49,6 +49,28 @@ #pragma once +#include +#include + +//============================================================================== +class ARADemoPluginAudioModification : public ARAAudioModification +{ +public: + ARADemoPluginAudioModification (ARAAudioSource* audioSource, + ARA::ARAAudioModificationHostRef hostRef, + const ARAAudioModification* optionalModificationToClone) + : ARAAudioModification (audioSource, hostRef, optionalModificationToClone) + { + if (optionalModificationToClone != nullptr) + dimmed = static_cast (optionalModificationToClone)->dimmed; + } + + bool isDimmed() const { return dimmed; } + void setDimmed (bool shouldDim) { dimmed = shouldDim; } + +private: + bool dimmed = false; +}; //============================================================================== struct PreviewState @@ -148,12 +170,13 @@ private: inline std::optional> readPlaybackRangeIntoBuffer (Range playbackRange, const ARAPlaybackRegion* playbackRegion, AudioBuffer& buffer, - const std::function& getReader) + const std::function& getReader) { const auto rangeInAudioModificationTime = playbackRange - playbackRegion->getStartInPlaybackTime() + playbackRegion->getStartInAudioModificationTime(); - const auto audioSource = playbackRegion->getAudioModification()->getAudioSource(); + const auto audioModification = playbackRegion->getAudioModification(); + const auto audioSource = audioModification->getAudioSource(); const auto audioModificationSampleRate = audioSource->getSampleRate(); const Range sampleRangeInAudioModification { @@ -192,7 +215,12 @@ inline std::optional> readPlaybackRangeIntoBuffer (Range pl 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::withStartAndLength (outputOffset, readLength); + } return {}; } @@ -347,6 +375,11 @@ public: continue; } + // Apply dim if enabled + if (playbackRegion->getAudioModification()->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. @@ -384,7 +417,7 @@ private: // We're subclassing here only to provide a proper default c'tor for our shared resource SharedResourcePointer sharedTimesliceThread; - std::map audioSourceReaders; + std::map audioSourceReaders; bool useBufferedAudioSourceReader = true; int numChannels = 2; double sampleRate = 48000.0; @@ -415,7 +448,7 @@ public: void didAddRegionSequence (ARA::PlugIn::RegionSequence* rs) noexcept override { - auto* sequence = dynamic_cast (rs); + auto* sequence = static_cast (rs); sequence->addListener (this); regionSequences.insert (sequence); asyncConfigCallback.startConfigure(); @@ -503,8 +536,12 @@ public: if (regionIsAssignedToEditor) { const auto previewTime = previewState->previewTime.load(); + const auto previewDimmed = previewedRegion->getAudioModification() + ->isDimmed(); - if (lastPreviewTime != previewTime || lastPlaybackRegion != previewedRegion) + if (lastPreviewTime != previewTime + || lastPlaybackRegion != previewedRegion + || lastPreviewDimmed != previewDimmed) { Range previewRangeInPlaybackTime { previewTime - 0.25, previewTime + 0.25 }; previewBuffer->clear(); @@ -521,6 +558,7 @@ public: { lastPreviewTime = previewTime; lastPlaybackRegion = previewedRegion; + lastPreviewDimmed = previewDimmed; previewLooper = Looper (previewBuffer.get(), *rangeInOutput); } } @@ -560,12 +598,13 @@ private: AsyncConfigurationCallback asyncConfigCallback { [this] { configure(); } }; double lastPreviewTime = 0.0; ARAPlaybackRegion* lastPlaybackRegion = nullptr; + bool lastPreviewDimmed = false; std::unique_ptr> previewBuffer; Looper previewLooper; double sampleRate = 48000.0; SharedResourcePointer timeSliceThread; - std::map> audioSourceReaders; + std::map> audioSourceReaders; std::set regionSequences; }; @@ -579,6 +618,15 @@ public: PreviewState previewState; protected: + ARAAudioModification* doCreateAudioModification (ARAAudioSource* audioSource, + ARA::ARAAudioModificationHostRef hostRef, + const ARAAudioModification* optionalModificationToClone) noexcept override + { + return new ARADemoPluginAudioModification (audioSource, + hostRef, + static_cast (optionalModificationToClone)); + } + ARAPlaybackRenderer* doCreatePlaybackRenderer() noexcept override { return new PlaybackRenderer (getDocumentController()); @@ -592,36 +640,108 @@ protected: bool doRestoreObjectsFromStream (ARAInputStream& input, const ARARestoreObjectsFilter* filter) noexcept override { - ignoreUnused (input, filter); - return false; + // 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 (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 { - ignoreUnused (output, filter); - return false; + // This example implementation only deals with audio modification states + const auto& audioModificationsToPersist { filter->getAudioModificationsToStore() }; + + 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; } }; struct PlayHeadState { - void update (AudioPlayHead* aph) + void update (const Optional& info) { - const auto info = aph->getPosition(); - - if (info.hasValue() && info->getIsPlaying()) + if (info.hasValue()) { - isPlaying.store (true); - timeInSeconds.store (info->getTimeInSeconds().orFallback (0)); + 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); + isPlaying.store (false, std::memory_order_relaxed); + isLooping.store (false, std::memory_order_relaxed); } } - std::atomic isPlaying { false }; - std::atomic timeInSeconds { 0.0 }; + std::atomic isPlaying { false }, + isLooping { false }; + std::atomic timeInSeconds { 0.0 }, + loopPpqStart { 0.0 }, + loopPpqEnd { 0.0 }; }; //============================================================================== @@ -639,13 +759,13 @@ public: //============================================================================== void prepareToPlay (double sampleRate, int samplesPerBlock) override { - playHeadState.isPlaying.store (false); + playHeadState.update (nullopt); prepareToPlayForARA (sampleRate, samplesPerBlock, getMainBusNumOutputChannels(), getProcessingPrecision()); } void releaseResources() override { - playHeadState.isPlaying.store (false); + playHeadState.update (nullopt); releaseResourcesForARA(); } @@ -665,7 +785,7 @@ public: ScopedNoDenormals noDenormals; auto* audioPlayHead = getPlayHead(); - playHeadState.update (audioPlayHead); + playHeadState.update (audioPlayHead->getPosition()); if (! processBlockForARA (buffer, isRealtime(), audioPlayHead)) processBlockBypassed (buffer, midiMessages); @@ -677,7 +797,15 @@ public: 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; } + + double getTailLengthSeconds() const override + { + double tail; + if (getTailLengthSecondsForARA (tail)) + return tail; + + return 0.0; + } //============================================================================== int getNumPrograms() override { return 0; } @@ -703,6 +831,340 @@ private: 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 listeners; +}; + +class RulersView : public Component, + public SettableTooltipClient, + private Timer, + private TimeToViewScaling::Listener, + private ARAMusicalContext::Listener +{ +public: + class CycleMarkerComponent : 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 tempoReader (selectedMusicalContext); + const ARA::TempoConverter tempoConverter (tempoReader); + + // chord ruler: one rect per chord, skipping empty "no chords" + const auto chordBounds = drawBounds.removeFromTop (rulerHeight); + const ARA::PlugIn::HostContentReader 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 barSignaturesReader (selectedMusicalContext); + + if (barSignaturesReader) + { + const ARA::BarSignaturesConverter 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 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 = (quarterPos == barSignature.position) ? heavyLineWidth : lightLineWidth; + const int beatsSinceBarStart = roundToInt( barSignaturesConverter.getBeatDistanceFromBarStartForQuarter (quarterPos)); + const int lineHeight = (beatsSinceBarStart == 0) ? rulerHeight : rulerHeight / 2; + rects.addWithoutMerging (Rectangle (x - lineWidth / 2, 2 * rulerHeight - lineHeight, lineWidth, lineHeight)); + } + + g.fillRectList (rects); + } + } + + // time ruler: one tick for each second + { + RectangleList 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 (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 tempoReader (selectedMusicalContext); + const ARA::TempoConverter 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 : 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 : private ARAAudioSource::Listener { @@ -756,21 +1218,37 @@ private: }; class PlaybackRegionView : public Component, - public ChangeListener + public ChangeListener, + public SettableTooltipClient, + private ARAAudioSource::Listener, + private ARAPlaybackRegion::Listener, + private ARAEditorView::Listener { public: - PlaybackRegionView (ARAPlaybackRegion& region, WaveformCache& cache) - : playbackRegion (region), waveformCache (cache) + 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 { - waveformCache.getOrCreateThumbnail (playbackRegion.getAudioModification()->getAudioSource()) - .removeChangeListener (this); + 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 @@ -781,6 +1259,7 @@ public: auto& previewState = ARADocumentControllerSpecialisation::getSpecialisedDocumentController (playbackRegion.getDocumentController())->previewState; previewState.previewTime.store (previewTime); previewState.previewedRegion.store (&playbackRegion); + previewRegionOverlay.update(); } void mouseUp (const MouseEvent&) override @@ -788,6 +1267,19 @@ public: auto& previewState = ARADocumentControllerSpecialisation::getSpecialisedDocumentController (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(); + 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 @@ -795,17 +1287,69 @@ public: 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 (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.fillAll (convertOptionalARAColour (playbackRegion.getEffectiveColor(), Colours::black)); + + const auto* audioModification = playbackRegion.getAudioModification(); + 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()); } @@ -815,18 +1359,70 @@ public: } private: + class PreviewRegionOverlay : 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 (playbackRegion.getDocumentController()); + } + + ARAEditorView& araEditorView; ARAPlaybackRegion& playbackRegion; WaveformCache& waveformCache; + PreviewRegionOverlay previewRegionOverlay; + bool isSelected = false; }; class RegionSequenceView : public Component, - public ARARegionSequence::Listener, public ChangeBroadcaster, + private TimeToViewScaling::Listener, + private ARARegionSequence::Listener, private ARAPlaybackRegion::Listener { public: - RegionSequenceView (ARARegionSequence& rs, WaveformCache& cache, double pixelPerSec) - : regionSequence (rs), waveformCache (cache), zoomLevelPixelPerSecond (pixelPerSec) + RegionSequenceView (ARAEditorView& editorView, TimeToViewScaling& scaling, ARARegionSequence& rs, WaveformCache& cache) + : araEditorView (editorView), timeToViewScaling (scaling), regionSequence (rs), waveformCache (cache) { regionSequence.addListener (this); @@ -834,10 +1430,14 @@ public: createAndAddPlaybackRegionView (playbackRegion); updatePlaybackDuration(); + + timeToViewScaling.addListener (this); } ~RegionSequenceView() override { + timeToViewScaling.removeListener (this); + regionSequence.removeListener (this); for (const auto& it : playbackRegionViews) @@ -846,6 +1446,16 @@ public: //============================================================================== // 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 { @@ -869,15 +1479,16 @@ public: updatePlaybackDuration(); } - void willUpdatePlaybackRegionProperties (ARAPlaybackRegion*, ARAPlaybackRegion::PropertiesPtr) override - { - } - void didUpdatePlaybackRegionProperties (ARAPlaybackRegion*) override { updatePlaybackDuration(); } + void zoomLevelChanged (double) override + { + resized(); + } + void resized() override { for (auto& pbr : playbackRegionViews) @@ -885,8 +1496,8 @@ public: const auto playbackRegion = pbr.first; pbr.second->setBounds ( getLocalBounds() - .withTrimmedLeft (roundToInt (playbackRegion->getStartInPlaybackTime() * zoomLevelPixelPerSecond)) - .withWidth (roundToInt (playbackRegion->getDurationInPlaybackTime() * zoomLevelPixelPerSecond))); + .withTrimmedLeft (timeToViewScaling.getXForTime (playbackRegion->getStartInPlaybackTime())) + .withWidth (timeToViewScaling.getXForTime (playbackRegion->getDurationInPlaybackTime()))); } } @@ -895,16 +1506,12 @@ public: return playbackDuration; } - void setZoomLevel (double pixelPerSecond) - { - zoomLevelPixelPerSecond = pixelPerSecond; - resized(); - } - private: void createAndAddPlaybackRegionView (ARAPlaybackRegion* playbackRegion) { - playbackRegionViews[playbackRegion] = std::make_unique (*playbackRegion, waveformCache); + playbackRegionViews[playbackRegion] = std::make_unique (araEditorView, + *playbackRegion, + waveformCache); playbackRegion->addListener (this); addAndMakeVisible (*playbackRegionViews[playbackRegion]); } @@ -922,11 +1529,12 @@ private: sendChangeMessage(); } + ARAEditorView& araEditorView; + TimeToViewScaling& timeToViewScaling; ARARegionSequence& regionSequence; WaveformCache& waveformCache; std::unordered_map> playbackRegionViews; double playbackDuration = 0.0; - double zoomLevelPixelPerSecond; }; class ZoomControls : public Component @@ -956,14 +1564,132 @@ private: TextButton zoomInButton { "+" }, zoomOutButton { "-" }; }; -class TrackHeader : public Component +class PlayheadPositionLabel : public Label, + private Timer { public: - explicit TrackHeader (const ARARegionSequence& regionSequenceIn) : regionSequence (regionSequenceIn) + PlayheadPositionLabel (PlayHeadState& playHeadStateIn) + : playHeadState (playHeadStateIn) { - update(); + 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 tempoReader (selectedMusicalContext); + const ARA::PlugIn::HostContentReader barSignaturesReader (selectedMusicalContext); + + if (tempoReader && barSignaturesReader) + { + const ARA::TempoConverter tempoConverter (tempoReader); + const ARA::BarSignaturesConverter 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 chordsReader (selectedMusicalContext); + + if (chordsReader && chordsReader.getEventCount() > 0) + { + const auto begin = chordsReader.begin(); + const auto end = chordsReader.end(); + auto it = begin; + + while (it->position <= quarterPosition && it != end) + ++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 : 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 @@ -973,30 +1699,43 @@ public: void paint (Graphics& g) override { - g.setColour (getLookAndFeel().findColour (ResizableWindow::backgroundColourId)); - g.fillRoundedRectangle (getLocalBounds().reduced (2).toType(), 6.0f); - g.setColour (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).contrasting()); - g.drawRoundedRectangle (getLocalBounds().reduced (2).toType(), 6.0f, 1.0f); + 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 update() + void updateTrackName (ARA::ARAUtf8String optionalName) { - const auto getWithDefaultValue = - [] (const ARA::PlugIn::OptionalProperty& optional, String defaultValue) - { - if (const ARA::ARAUtf8String value = optional) - return String (value); - - return defaultValue; - }; - - trackNameLabel.setText (getWithDefaultValue (regionSequence.getName(), "No track name"), + trackNameLabel.setText (optionalName ? optionalName : "No track name", NotificationType::dontSendNotification); } - const ARARegionSequence& regionSequence; + ARAEditorView& araEditorView; + ARARegionSequence& regionSequence; Label trackNameLabel; + bool isSelected = false; }; constexpr auto trackHeight = 60; @@ -1014,18 +1753,6 @@ public: 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 @@ -1053,35 +1780,35 @@ private: }; class OverlayComponent : public Component, - private Timer + private Timer, + private TimeToViewScaling::Listener { public: class PlayheadMarkerComponent : public Component { - void paint (Graphics& g) override { g.fillAll (juce::Colours::yellow.darker (0.2f)); } + void paint (Graphics& g) override { g.fillAll (Colours::yellow.darker (0.2f)); } }; - OverlayComponent (PlayHeadState& playHeadStateIn) - : playHeadState (&playHeadStateIn) + 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 { - doResize(); - } - - void setZoomLevel (double pixelPerSecondIn) - { - pixelPerSecond = pixelPerSecondIn; + updatePlayHeadPosition(); } void setHorizontalOffset (int offset) @@ -1089,12 +1816,38 @@ public: horizontalOffset = offset; } -private: - void doResize() + void setSelectedTimeRange (std::optional timeRange) { - if (playHeadState->isPlaying.load()) + selectedTimeRange = timeRange; + repaint(); + } + + void zoomLevelChanged (double) override + { + updatePlayHeadPosition(); + repaint(); + } + + void paint (Graphics& g) override + { + if (selectedTimeRange) { - const auto markerX = playHeadState->timeInSeconds.load() * pixelPerSecond; + 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); @@ -1108,28 +1861,38 @@ private: void timerCallback() override { - doResize(); + updatePlayHeadPosition(); } static constexpr double markerWidth = 2.0; - PlayHeadState* playHeadState; - double pixelPerSecond = 1.0; + PlayHeadState& playHeadState; + TimeToViewScaling& timeToViewScaling; int horizontalOffset = 0; + std::optional selectedTimeRange; PlayheadMarkerComponent playheadMarker; }; class DocumentView : public Component, public ChangeListener, + public ARAMusicalContext::Listener, private ARADocument::Listener, private ARAEditorView::Listener { public: - explicit DocumentView (ARADocument& document, PlayHeadState& playHeadState) - : araDocument (document), - overlay (playHeadState) + DocumentView (ARAEditorView& editorView, PlayHeadState& playHeadState) + : araEditorView (editorView), + araDocument (*editorView.getDocumentController()->getDocument()), + rulersView (playHeadState, timeToViewScaling, araDocument), + overlay (playHeadState, timeToViewScaling), + playheadPositionLabel (playHeadState) { - addAndMakeVisible (tracksBackground); + if (araDocument.getMusicalContexts().size() > 0) + selectMusicalContext (araDocument.getMusicalContexts().front()); + + addAndMakeVisible (rulersHeader); + + viewport.content.addAndMakeVisible (rulersView); viewport.onVisibleAreaChanged = [this] (const auto& r) { @@ -1139,25 +1902,39 @@ public: }; addAndMakeVisible (viewport); - - overlay.setZoomLevel (zoomLevelPixelPerSecond); 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*) override + { + selectMusicalContext (nullptr); + } + void didReorderRegionSequencesInDocument (ARADocument*) override { invalidateRegionSequenceViews(); @@ -1187,12 +1964,35 @@ public: //============================================================================== // ARAEditorView::Listener overrides - void onNewSelection (const ARA::PlugIn::ViewSelection&) override + void onNewSelection (const ARAViewSelection& viewSelection) override { + auto getNewSelectedMusicalContext = [&viewSelection]() -> ARAMusicalContext* + { + if (! viewSelection.getRegionSequences().empty()) + return viewSelection.getRegionSequences().front()->getMusicalContext(); + else if (! viewSelection.getPlaybackRegions().empty()) + return viewSelection.getPlaybackRegions().front()->getRegionSequence()->getMusicalContext(); + + return nullptr; + }; + + if (auto* newSelectedMusicalContext = getNewSelectedMusicalContext()) + if (newSelectedMusicalContext != selectedMusicalContext) + selectMusicalContext (newSelectedMusicalContext); + + // If no context is used yet and the selection does not yield a new one, the DocumentView + // uses the first musical context in the document. + + if (const auto timeRange = viewSelection.getTimeRange()) + overlay.setSelectedTimeRange (*timeRange); + else + overlay.setSelectedTimeRange (std::nullopt); } - void onHideRegionSequences (const std::vector&) override + void onHideRegionSequences (const std::vector& regionSequences) override { + hiddenRegionSequences = regionSequences; + invalidateRegionSequenceViews(); } //============================================================================== @@ -1204,29 +2004,27 @@ public: void resized() override { auto bounds = getLocalBounds(); - const auto bottomControlsBounds = bounds.removeFromBottom (40); - const auto headerBounds = bounds.removeFromLeft (headerWidth).reduced (2); - zoomControls.setBounds (bottomControlsBounds); + 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); - tracksBackground.setBounds (bounds); + viewport.setBounds (bounds); - overlay.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(); } //============================================================================== - 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: @@ -1246,10 +2044,26 @@ private: 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) { - zoomLevelPixelPerSecond = jlimit (minimumZoom, minimumZoom * 32, zoomLevelPixelPerSecond * factor); - setZoomLevel (zoomLevelPixelPerSecond); + timeToViewScaling.zoom (factor); + update(); } template @@ -1271,11 +2085,6 @@ private: for (const auto& view : regionSequenceViews) timelineLength = std::max (timelineLength, view.second->getPlaybackDuration()); - const Rectangle timelineSize (roundToInt (timelineLength * zoomLevelPixelPerSecond), - (int) regionSequenceViews.size() * trackHeight); - viewport.content.setSize (timelineSize.getWidth(), timelineSize.getHeight()); - viewport.content.resized(); - resized(); } @@ -1290,14 +2099,14 @@ private: auto& regionSequenceView = insertIntoMap ( regionSequenceViews, RegionSequenceViewKey { regionSequence }, - std::make_unique (*regionSequence, waveformCache, zoomLevelPixelPerSecond)); + std::make_unique (araEditorView, timeToViewScaling, *regionSequence, waveformCache)); regionSequenceView.addChangeListener (this); viewport.content.addAndMakeVisible (regionSequenceView); auto& trackHeader = insertIntoMap (trackHeaders, RegionSequenceViewKey { regionSequence }, - std::make_unique (*regionSequence)); + std::make_unique (araEditorView, *regionSequence)); addAndMakeVisible (trackHeader); } @@ -1336,9 +2145,8 @@ private: trackHeaders.clear(); for (auto* regionSequence : araDocument.getRegionSequences()) - { - addTrackViews (regionSequence); - } + if (std::find (hiddenRegionSequences.begin(), hiddenRegionSequences.end(), regionSequence) == hiddenRegionSequences.end()) + addTrackViews (regionSequence); update(); @@ -1346,30 +2154,28 @@ private: } } - 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; - + ARAEditorView& araEditorView; ARADocument& araDocument; bool regionSequenceViewsAreValid = false; - double timelineLength = 0; - double zoomLevelPixelPerSecond = minimumZoom * 4; + + TimeToViewScaling timeToViewScaling; + double timelineLength = 0.0; + + ARAMusicalContext* selectedMusicalContext = nullptr; + + std::vector hiddenRegionSequences; WaveformCache waveformCache; - TracksBackgroundComponent tracksBackground; std::map> trackHeaders; std::map> regionSequenceViews; + RulersHeader rulersHeader; + RulersView rulersView; VerticalLayoutViewport viewport; OverlayComponent overlay; ZoomControls zoomControls; + PlayheadPositionLabel playheadPositionLabel; + TooltipWindow tooltip; int viewportHeightOffset = 0; }; @@ -1384,17 +2190,14 @@ public: AudioProcessorEditorARAExtension (&p) { if (auto* editorView = getARAEditorView()) - { - auto* document = ARADocumentControllerSpecialisation::getSpecialisedDocumentController(editorView->getDocumentController())->getDocument(); - documentView = std::make_unique (*document, p.playHeadState ); - } + documentView = std::make_unique (*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 (400, 300); + setSize (800, 300); } //============================================================================== diff --git a/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.h b/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.h index e23aeded75..4306d922b2 100644 --- a/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.h +++ b/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.h @@ -204,7 +204,7 @@ private: }; //============================================================================== -/** Base class for a renderer fulfilling the ARAEditorView role as described in the ARA SDK. +/** Base class for fulfilling the ARAEditorView role as described in the ARA SDK. Instances of this class are constructed by the DocumentController. If you are subclassing ARAEditorView, make sure to call the base class implementation of overridden functions. @@ -244,7 +244,7 @@ public: /** Called when the editor view's selection changes. @param viewSelection The current selection state */ - virtual void onNewSelection (const ARA::PlugIn::ViewSelection& viewSelection) + virtual void onNewSelection (const ARAViewSelection& viewSelection) { ignoreUnused (viewSelection); }