diff --git a/src/AdornedRulerPanel.cpp b/src/AdornedRulerPanel.cpp index 50b61c8d7..788e7e59d 100644 --- a/src/AdornedRulerPanel.cpp +++ b/src/AdornedRulerPanel.cpp @@ -1900,7 +1900,8 @@ void AdornedRulerPanel::HandleSnapping() if (handle) { auto &pSnapManager = handle->mSnapManager; if (! pSnapManager) - pSnapManager = std::make_unique(mTracks, mViewInfo); + pSnapManager = + std::make_unique(*mProject, *mTracks, *mViewInfo); auto results = pSnapManager->Snap(NULL, mQuickPlayPos, false); mQuickPlayPos = results.outTime; diff --git a/src/LabelTrack.cpp b/src/LabelTrack.cpp index 0992b3b9d..de304be82 100644 --- a/src/LabelTrack.cpp +++ b/src/LabelTrack.cpp @@ -84,6 +84,28 @@ LabelTrack::LabelTrack(const LabelTrack &orig) : } } +template< typename Container > +static Container MakeIntervals(const LabelArray &labels) +{ + Container result; + size_t ii = 0; + for (const auto &label : labels) + result.emplace_back( + label.getT0(), label.getT1(), + std::make_unique( ii++ ) ); + return result; +} + +auto LabelTrack::GetIntervals() const -> ConstIntervals +{ + return MakeIntervals(mLabels); +} + +auto LabelTrack::GetIntervals() -> Intervals +{ + return MakeIntervals(mLabels); +} + void LabelTrack::SetLabel( size_t iLabel, const LabelStruct &newLabel ) { if( iLabel >= mLabels.size() ) { diff --git a/src/LabelTrack.h b/src/LabelTrack.h index 98f334f02..09cdd78a0 100644 --- a/src/LabelTrack.h +++ b/src/LabelTrack.h @@ -155,6 +155,13 @@ public: int FindNextLabel(const SelectedRegion& currentSelection); int FindPrevLabel(const SelectedRegion& currentSelection); + struct IntervalData final : Track::IntervalData { + size_t index; + explicit IntervalData(size_t index) : index{index} {}; + }; + ConstIntervals GetIntervals() const override; + Intervals GetIntervals() override; + public: void SortLabels(); diff --git a/src/NoteTrack.cpp b/src/NoteTrack.cpp index 4e113903f..789a1ff8e 100644 --- a/src/NoteTrack.cpp +++ b/src/NoteTrack.cpp @@ -683,6 +683,20 @@ QuantizedTimeAndBeat NoteTrack::NearestBeatTime( double time ) const return { seq_time + GetOffset(), beat }; } +auto NoteTrack::GetIntervals() const -> ConstIntervals +{ + ConstIntervals results; + results.emplace_back( GetStartTime(), GetEndTime() ); + return results; +} + +auto NoteTrack::GetIntervals() -> Intervals +{ + Intervals results; + results.emplace_back( GetStartTime(), GetEndTime() ); + return results; +} + void NoteTrack::AddToDuration( double delta ) { auto &seq = GetSeq(); diff --git a/src/NoteTrack.h b/src/NoteTrack.h index 91295ce81..f1cf21658 100644 --- a/src/NoteTrack.h +++ b/src/NoteTrack.h @@ -185,6 +185,9 @@ public: mVisibleChannels = CHANNEL_BIT(c); } + ConstIntervals GetIntervals() const override; + Intervals GetIntervals() override; + private: TrackKind GetKind() const override { return TrackKind::Note; } diff --git a/src/Snap.cpp b/src/Snap.cpp index 944494f2f..5bb84c0b4 100644 --- a/src/Snap.cpp +++ b/src/Snap.cpp @@ -16,57 +16,57 @@ #include "Project.h" #include "ProjectSettings.h" -#include "LabelTrack.h" -#include "NoteTrack.h" -#include "WaveClip.h" +#include "Track.h" #include "ViewInfo.h" -#include "WaveTrack.h" inline bool operator < (SnapPoint s1, SnapPoint s2) { return s1.t < s2.t; } -TrackClip::TrackClip(Track *t, WaveClip *c) -{ - track = origTrack = t; - dstTrack = NULL; - clip = c; -} - -TrackClip::~TrackClip() -{ - -} - -SnapManager::SnapManager(const TrackList *tracks, - const ZoomInfo *zoomInfo, - const TrackClipArray *clipExclusions, - const TrackArray *trackExclusions, +SnapManager::SnapManager(const AudacityProject &project, + SnapPointArray candidates, + const ZoomInfo &zoomInfo, bool noTimeSnap, int pixelTolerance) -: mConverter(NumericConverter::TIME) +: mProject{ &project } +, mZoomInfo{ &zoomInfo } +, mPixelTolerance{ pixelTolerance } +, mNoTimeSnap{ noTimeSnap } +, mCandidates{ std::move( candidates ) } +, mSnapPoints{} +, mConverter{ NumericConverter::TIME } { - mTracks = tracks; - mZoomInfo = zoomInfo; - mClipExclusions = clipExclusions; - mTrackExclusions = trackExclusions; - mPixelTolerance = pixelTolerance; - mNoTimeSnap = noTimeSnap; - - mProject = tracks->GetOwner(); - wxASSERT(mProject); - - mSnapTo = 0; - mRate = 0.0; - mFormat = {}; - - // Two time points closer than this are considered the same - mEpsilon = 1 / 44100.0; - Reinit(); } +namespace { +SnapPointArray FindCandidates( const TrackList &tracks ) +{ + SnapPointArray candidates; + for ( const auto track : tracks.Any() ) { + auto intervals = track->GetIntervals(); + for (const auto &interval : intervals) { + candidates.emplace_back( interval.Start(), track ); + if ( interval.Start() != interval.End() ) + candidates.emplace_back( interval.End(), track ); + } + } + return candidates; +} +} + +SnapManager::SnapManager(const AudacityProject &project, + const TrackList &tracks, + const ZoomInfo &zoomInfo, + bool noTimeSnap, + int pixelTolerance) + : SnapManager{ project, + FindCandidates( tracks ), + zoomInfo, noTimeSnap, pixelTolerance } +{ +} + SnapManager::~SnapManager() { } @@ -105,58 +105,9 @@ void SnapManager::Reinit() // Add a SnapPoint at t=0 mSnapPoints.push_back(SnapPoint{}); - auto trackRange = - mTracks->Any() - - [&](const Track *pTrack){ - return mTrackExclusions && - make_iterator_range( *mTrackExclusions ).contains( pTrack ); - }; - trackRange.Visit( - [&](const LabelTrack *labelTrack) { - for (int i = 0, cnt = labelTrack->GetNumLabels(); i < cnt; ++i) - { - const LabelStruct *label = labelTrack->GetLabel(i); - const double t0 = label->getT0(); - const double t1 = label->getT1(); - CondListAdd(t0, labelTrack); - if (t1 != t0) - { - CondListAdd(t1, labelTrack); - } - } - }, - [&](const WaveTrack *waveTrack) { - for (const auto &clip: waveTrack->GetClips()) - { - if (mClipExclusions) - { - bool skip = false; - for (size_t j = 0, cnt = mClipExclusions->size(); j < cnt; ++j) - { - if ((*mClipExclusions)[j].track == waveTrack && - (*mClipExclusions)[j].clip == clip.get()) - { - skip = true; - break; - } - } - - if (skip) - continue; - } - - CondListAdd(clip->GetStartTime(), waveTrack); - CondListAdd(clip->GetEndTime(), waveTrack); - } - } -#ifdef USE_MIDI - , - [&](const NoteTrack *track) { - CondListAdd(track->GetStartTime(), track); - CondListAdd(track->GetEndTime(), track); - } -#endif - ); + // Adjust and filter the candidate points + for (const auto &candidate : mCandidates) + CondListAdd( candidate.t, candidate.track ); // Sort all by time std::sort(mSnapPoints.begin(), mSnapPoints.end()); diff --git a/src/Snap.h b/src/Snap.h index 7f8c4bac1..5e0e45bc2 100644 --- a/src/Snap.h +++ b/src/Snap.h @@ -21,32 +21,10 @@ class AudacityProject; class Track; -using TrackArray = std::vector< Track* >; -class TrackClipArray; -class WaveClip; -class WaveTrack; class TrackList; class ZoomInfo; class wxDC; -class TrackClip -{ -public: - TrackClip(Track *t, WaveClip *c); - - ~TrackClip(); - - Track *track; - Track *origTrack; - WaveClip *clip; - - // These fields are used only during time-shift dragging - WaveTrack *dstTrack; - std::shared_ptr holder; -}; - -class TrackClipArray : public std::vector < TrackClip > {}; - const int kPixelTolerance = 4; class SnapPoint @@ -77,12 +55,18 @@ struct SnapResults { class SnapManager { public: - SnapManager(const TrackList *tracks, - const ZoomInfo *zoomInfo, - const TrackClipArray *clipExclusions = NULL, - const TrackArray *trackExclusions = NULL, + SnapManager(const AudacityProject &project, + SnapPointArray candidates, + const ZoomInfo &zoomInfo, bool noTimeSnap = false, int pixelTolerance = kPixelTolerance); + + SnapManager(const AudacityProject &project, + const TrackList &tracks, + const ZoomInfo &zoomInfo, + bool noTimeSnap = false, + int pixelTolerance = kPixelTolerance); + ~SnapManager(); // The track may be NULL. @@ -111,23 +95,22 @@ private: private: const AudacityProject *mProject; - const TrackList *mTracks; - const TrackClipArray *mClipExclusions; - const TrackArray *mTrackExclusions; const ZoomInfo *mZoomInfo; int mPixelTolerance; bool mNoTimeSnap; - double mEpsilon; + //! Two time points closer than this are considered the same + double mEpsilon{ 1 / 44100.0 }; + SnapPointArray mCandidates; SnapPointArray mSnapPoints; // Info for snap-to-time NumericConverter mConverter; - bool mSnapToTime; + bool mSnapToTime{ false }; - int mSnapTo; - double mRate; - NumericFormatSymbol mFormat; + int mSnapTo{ 0 }; + double mRate{ 0.0 }; + NumericFormatSymbol mFormat{}; }; #endif diff --git a/src/Track.cpp b/src/Track.cpp index 0caaed327..1f9a14869 100644 --- a/src/Track.cpp +++ b/src/Track.cpp @@ -1224,6 +1224,16 @@ std::shared_ptr Track::SubstituteOriginalTrack() const return SharedPointer(); } +auto Track::GetIntervals() const -> ConstIntervals +{ + return {}; +} + +auto Track::GetIntervals() -> Intervals +{ + return {}; +} + // Serialize, not with tags of its own, but as attributes within a tag. void Track::WriteCommonXMLAttributes( XMLWriter &xmlFile, bool includeNameAndSelected) const @@ -1269,6 +1279,8 @@ void Track::AdjustPositions() } } +TrackIntervalData::~TrackIntervalData() = default; + bool TrackList::HasPendingTracks() const { if ( !mPendingUpdates.empty() ) diff --git a/src/Track.h b/src/Track.h index d8637ddf6..15a7eff57 100644 --- a/src/Track.h +++ b/src/Track.h @@ -181,6 +181,50 @@ private: long mValue; }; +//! Optional extra information about an interval, appropriate to a subtype of Track +struct TrackIntervalData { + virtual ~TrackIntervalData(); +}; + +//! A start and an end time, and non-mutative access to optional extra information +/*! @invariant `Start() <= End()` */ +class ConstTrackInterval { +public: + + /*! @pre `start <= end` */ + ConstTrackInterval( double start, double end, + std::unique_ptr pExtra = {} ) + : start{ start }, end{ end }, pExtra{ std::move( pExtra ) } + { + wxASSERT( start <= end ); + } + + ConstTrackInterval( ConstTrackInterval&& ) = default; + ConstTrackInterval &operator=( ConstTrackInterval&& ) = default; + + double Start() const { return start; } + double End() const { return end; } + const TrackIntervalData *Extra() const { return pExtra.get(); } + +private: + double start, end; +protected: + // TODO C++17: use std::any instead + std::unique_ptr< TrackIntervalData > pExtra; +}; + +//! A start and an end time, and mutative access to optional extra information +/*! @invariant `Start() <= End()` */ +class TrackInterval : public ConstTrackInterval { +public: + using ConstTrackInterval::ConstTrackInterval; + + TrackInterval(TrackInterval&&) = default; + TrackInterval &operator= (TrackInterval&&) = default; + + TrackIntervalData *Extra() const { return pExtra.get(); } +}; + //! Template generated base class for Track lets it host opaque UI related objects using AttachedTrackObjects = ClientData::Site< Track, ClientData::Base, ClientData::SkipCopying, std::shared_ptr @@ -268,6 +312,23 @@ class AUDACITY_DLL_API Track /* not final */ // original; else return this track std::shared_ptr SubstituteOriginalTrack() const; + using IntervalData = TrackIntervalData; + using Interval = TrackInterval; + using Intervals = std::vector< Interval >; + using ConstInterval = ConstTrackInterval; + using ConstIntervals = std::vector< ConstInterval >; + + //! Report times on the track where important intervals begin and end, for UI to snap to + /*! + Some intervals may be empty, and no ordering of the intervals is assumed. + */ + virtual ConstIntervals GetIntervals() const; + + /*! @copydoc GetIntervals() + This overload exposes the extra data of the intervals as non-const + */ + virtual Intervals GetIntervals(); + public: mutable wxSize vrulerSize; diff --git a/src/WaveTrack.cpp b/src/WaveTrack.cpp index 18b48c7d3..d86a4960d 100644 --- a/src/WaveTrack.cpp +++ b/src/WaveTrack.cpp @@ -323,6 +323,27 @@ int WaveTrack::ZeroLevelYCoordinate(wxRect rect) const (int)((mDisplayMax / (mDisplayMax - mDisplayMin)) * rect.height); } +template< typename Container > +static Container MakeIntervals(const std::vector &clips) +{ + Container result; + for (const auto &clip: clips) { + result.emplace_back( clip->GetStartTime(), clip->GetEndTime(), + std::make_unique( clip ) ); + } + return result; +} + +auto WaveTrack::GetIntervals() const -> ConstIntervals +{ + return MakeIntervals( mClips ); +} + +auto WaveTrack::GetIntervals() -> Intervals +{ + return MakeIntervals( mClips ); +} + Track::Holder WaveTrack::Clone() const { return std::make_shared( *this ); diff --git a/src/WaveTrack.h b/src/WaveTrack.h index a209ca02d..4ac2a45b5 100644 --- a/src/WaveTrack.h +++ b/src/WaveTrack.h @@ -523,6 +523,20 @@ private: // bottom and top. Maybe that is out of bounds. int ZeroLevelYCoordinate(wxRect rect) const; + class IntervalData final : public Track::IntervalData { + public: + explicit IntervalData( const std::shared_ptr &pClip ) + : pClip{ pClip } + {} + std::shared_ptr GetClip() const { return pClip; } + std::shared_ptr &GetClip() { return pClip; } + private: + std::shared_ptr pClip; + }; + + ConstIntervals GetIntervals() const override; + Intervals GetIntervals() override; + protected: // // Protected variables diff --git a/src/tracks/ui/SelectHandle.cpp b/src/tracks/ui/SelectHandle.cpp index f39b0725f..1cbdbb0fa 100644 --- a/src/tracks/ui/SelectHandle.cpp +++ b/src/tracks/ui/SelectHandle.cpp @@ -432,7 +432,8 @@ SelectHandle::SelectHandle const TrackList &trackList, const TrackPanelMouseState &st, const ViewInfo &viewInfo ) : mpView{ pTrackView } - , mSnapManager{ std::make_shared(&trackList, &viewInfo) } + , mSnapManager{ std::make_shared( + *trackList.GetOwner(), trackList, viewInfo) } { const wxMouseState &state = st.state; mRect = st.rect; diff --git a/src/tracks/ui/TimeShiftHandle.cpp b/src/tracks/ui/TimeShiftHandle.cpp index 2a7f22a79..7ae6e2434 100644 --- a/src/tracks/ui/TimeShiftHandle.cpp +++ b/src/tracks/ui/TimeShiftHandle.cpp @@ -21,6 +21,7 @@ Paul Licameli split from TrackPanel.cpp #include "../../ProjectHistory.h" #include "../../ProjectSettings.h" #include "../../RefreshCode.h" +#include "../../Snap.h" #include "../../TrackArtist.h" #include "../../TrackPanelDrawingContext.h" #include "../../TrackPanelMouseEvent.h" @@ -30,6 +31,18 @@ Paul Licameli split from TrackPanel.cpp #include "../../WaveTrack.h" #include "../../../images/Cursors.h" +TrackClip::TrackClip(Track *t, WaveClip *c) +{ + track = origTrack = t; + dstTrack = NULL; + clip = c; +} + +TrackClip::~TrackClip() +{ + +} + TimeShiftHandle::TimeShiftHandle ( const std::shared_ptr &pTrack, bool gripHit ) : mCapturedTrack{ pTrack } @@ -350,6 +363,64 @@ void TimeShiftHandle::DoSlideHorizontal DoOffset( state, &capturedTrack, state.hSlideAmount ); } +#include "LabelTrack.h" +namespace { +SnapPointArray FindCandidates( + const TrackList &tracks, + const TrackClipArray &clipExclusions, const TrackArray &trackExclusions ) +{ + // Special case restricted candidates for time shift + SnapPointArray candidates; + auto trackRange = + tracks.Any() + - [&](const Track *pTrack){ + return + make_iterator_range( trackExclusions ).contains( pTrack ); + }; + trackRange.Visit( + [&](const LabelTrack *labelTrack) { + for (const auto &label : labelTrack->GetLabels()) + { + const double t0 = label.getT0(); + const double t1 = label.getT1(); + candidates.emplace_back(t0, labelTrack); + if (t1 != t0) + candidates.emplace_back(t1, labelTrack); + } + }, + [&](const WaveTrack *waveTrack) { + for (const auto &clip: waveTrack->GetClips()) + { + bool skip = false; + for (const auto &exclusion : clipExclusions) + { + if (exclusion.track == waveTrack && + exclusion.clip == clip.get()) + { + skip = true; + break; + } + } + + if (skip) + continue; + + candidates.emplace_back(clip->GetStartTime(), waveTrack); + candidates.emplace_back(clip->GetEndTime(), waveTrack); + } + } +#ifdef USE_MIDI + , + [&](const NoteTrack *track) { + candidates.emplace_back(track->GetStartTime(), track); + candidates.emplace_back(track->GetEndTime(), track); + } +#endif + ); + return candidates; +} +} + UIHandle::Result TimeShiftHandle::Click (const TrackPanelMouseEvent &evt, AudacityProject *pProject) { @@ -414,11 +485,13 @@ UIHandle::Result TimeShiftHandle::Click mSlideUpDownOnly = event.CmdDown() && !multiToolModeActive; mRect = rect; mClipMoveState.mMouseClickX = event.m_x; - mSnapManager = std::make_shared(&trackList, - &viewInfo, - &mClipMoveState.capturedClipArray, - &mClipMoveState.trackExclusions, - true); // don't snap to time + mSnapManager = + std::make_shared(*trackList.GetOwner(), + FindCandidates( trackList, + mClipMoveState.capturedClipArray, mClipMoveState.trackExclusions), + viewInfo, + true, // don't snap to time + kPixelTolerance); mClipMoveState.snapLeft = -1; mClipMoveState.snapRight = -1; mSnapPreferRightEdge = diff --git a/src/tracks/ui/TimeShiftHandle.h b/src/tracks/ui/TimeShiftHandle.h index 4cf8e5088..7c3ec0c0a 100644 --- a/src/tracks/ui/TimeShiftHandle.h +++ b/src/tracks/ui/TimeShiftHandle.h @@ -13,10 +13,31 @@ Paul Licameli #include "../../UIHandle.h" -#include "../../Snap.h" - +class SnapManager; +class Track; +using TrackArray = std::vector; +class TrackList; class ViewInfo; class WaveClip; +class WaveTrack; + +class TrackClip +{ +public: + TrackClip(Track *t, WaveClip *c); + + ~TrackClip(); + + Track *track; + Track *origTrack; + WaveClip *clip; + + // These fields are used only during time-shift dragging + WaveTrack *dstTrack; + std::shared_ptr holder; +}; + +using TrackClipArray = std::vector ; struct ClipMoveState { // non-NULL only if click was in a WaveTrack and without Shift key: