1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-15 07:40:23 +02:00

Snap does not depend on subclasses of Track

This commit is contained in:
Paul Licameli 2020-09-13 08:53:28 -04:00
commit b75ab214cf
14 changed files with 317 additions and 133 deletions

View File

@ -1900,7 +1900,8 @@ void AdornedRulerPanel::HandleSnapping()
if (handle) {
auto &pSnapManager = handle->mSnapManager;
if (! pSnapManager)
pSnapManager = std::make_unique<SnapManager>(mTracks, mViewInfo);
pSnapManager =
std::make_unique<SnapManager>(*mProject, *mTracks, *mViewInfo);
auto results = pSnapManager->Snap(NULL, mQuickPlayPos, false);
mQuickPlayPos = results.outTime;

View File

@ -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<LabelTrack::IntervalData>( ii++ ) );
return result;
}
auto LabelTrack::GetIntervals() const -> ConstIntervals
{
return MakeIntervals<ConstIntervals>(mLabels);
}
auto LabelTrack::GetIntervals() -> Intervals
{
return MakeIntervals<Intervals>(mLabels);
}
void LabelTrack::SetLabel( size_t iLabel, const LabelStruct &newLabel )
{
if( iLabel >= mLabels.size() ) {

View File

@ -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();

View File

@ -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();

View File

@ -185,6 +185,9 @@ public:
mVisibleChannels = CHANNEL_BIT(c);
}
ConstIntervals GetIntervals() const override;
Intervals GetIntervals() override;
private:
TrackKind GetKind() const override { return TrackKind::Note; }

View File

@ -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());

View File

@ -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<WaveClip> 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

View File

@ -1224,6 +1224,16 @@ std::shared_ptr<const Track> 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() )

View File

@ -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<TrackIntervalData> 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<const Track> 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;

View File

@ -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<WaveClipHolder> &clips)
{
Container result;
for (const auto &clip: clips) {
result.emplace_back( clip->GetStartTime(), clip->GetEndTime(),
std::make_unique<WaveTrack::IntervalData>( clip ) );
}
return result;
}
auto WaveTrack::GetIntervals() const -> ConstIntervals
{
return MakeIntervals<ConstIntervals>( mClips );
}
auto WaveTrack::GetIntervals() -> Intervals
{
return MakeIntervals<Intervals>( mClips );
}
Track::Holder WaveTrack::Clone() const
{
return std::make_shared<WaveTrack>( *this );

View File

@ -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<WaveClip> &pClip )
: pClip{ pClip }
{}
std::shared_ptr<const WaveClip> GetClip() const { return pClip; }
std::shared_ptr<WaveClip> &GetClip() { return pClip; }
private:
std::shared_ptr<WaveClip> pClip;
};
ConstIntervals GetIntervals() const override;
Intervals GetIntervals() override;
protected:
//
// Protected variables

View File

@ -432,7 +432,8 @@ SelectHandle::SelectHandle
const TrackList &trackList,
const TrackPanelMouseState &st, const ViewInfo &viewInfo )
: mpView{ pTrackView }
, mSnapManager{ std::make_shared<SnapManager>(&trackList, &viewInfo) }
, mSnapManager{ std::make_shared<SnapManager>(
*trackList.GetOwner(), trackList, viewInfo) }
{
const wxMouseState &state = st.state;
mRect = st.rect;

View File

@ -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<Track> &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<SnapManager>(&trackList,
&viewInfo,
&mClipMoveState.capturedClipArray,
&mClipMoveState.trackExclusions,
true); // don't snap to time
mSnapManager =
std::make_shared<SnapManager>(*trackList.GetOwner(),
FindCandidates( trackList,
mClipMoveState.capturedClipArray, mClipMoveState.trackExclusions),
viewInfo,
true, // don't snap to time
kPixelTolerance);
mClipMoveState.snapLeft = -1;
mClipMoveState.snapRight = -1;
mSnapPreferRightEdge =

View File

@ -13,10 +13,31 @@ Paul Licameli
#include "../../UIHandle.h"
#include "../../Snap.h"
class SnapManager;
class Track;
using TrackArray = std::vector<Track*>;
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<WaveClip> holder;
};
using TrackClipArray = std::vector <TrackClip>;
struct ClipMoveState {
// non-NULL only if click was in a WaveTrack and without Shift key: