1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-04-30 15:49:41 +02:00
audacity/src/Snap.cpp
2015-07-19 11:44:05 -04:00

316 lines
8.1 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
Snap.cpp
Dominic Mazzoni
**********************************************************************/
#include "Snap.h"
#include <algorithm>
#include <math.h>
#include "LabelTrack.h"
#include "Project.h"
#include "TrackPanel.h"
#include "widgets/NumericTextCtrl.h"
// Change this to "true" to snap to nearest and "false" to snap to previous
// As of 2013/10/23, defaulting to "true" until a decision is made on
// which method is prefered.
#define SNAP_TO_NEAREST false
static int CompareSnapPoints(SnapPoint *s1, SnapPoint *s2)
{
return (s1->t - s2->t > 0? 1 : -1);
}
SnapManager::SnapManager(TrackList *tracks, TrackClipArray *exclusions,
const ZoomInfo &zoomInfo, int pixelTolerance, bool noTimeSnap)
: mConverter(NumericConverter::TIME)
, mPixelTolerance(std::max(0, pixelTolerance))
, mZoomInfo(zoomInfo)
{
int i;
// Grab time-snapping prefs (unless otherwise requested)
mSnapToTime = false;
AudacityProject *p = GetActiveProject();
wxASSERT(p);
if (p)
{
// Look up the format string
if (p->GetSnapTo() && !noTimeSnap) {
mSnapToTime = true;
mConverter.SetSampleRate(p->GetRate());
mConverter.SetFormatName(p->GetSelectionFormat());
}
}
mSnapPoints = new SnapPointArray(CompareSnapPoints);
// Two time points closer than this are considered the same
mEpsilon = 1 / 44100.0;
// Add a SnapPoint at t=0
mSnapPoints->Add(new SnapPoint(0.0, NULL));
TrackListIterator iter(tracks);
Track *track = iter.First();
while (track) {
if (track->GetKind() == Track::Label) {
LabelTrack *labelTrack = (LabelTrack *)track;
for(i = 0; i < labelTrack->GetNumLabels(); 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);
}
}
}
else if (track->GetKind() == Track::Wave) {
WaveTrack *waveTrack = (WaveTrack *)track;
WaveClipList::compatibility_iterator it;
for (it=waveTrack->GetClipIterator(); it; it=it->GetNext()) {
WaveClip *clip = it->GetData();
if (exclusions) {
bool skip = false;
for(int j=0; j<(int)exclusions->GetCount(); j++) {
if ((*exclusions)[j].track == waveTrack &&
(*exclusions)[j].clip == clip)
skip = true;
}
if (skip)
continue;
}
CondListAdd(clip->GetStartTime(), waveTrack);
CondListAdd(clip->GetEndTime(), waveTrack);
}
}
#ifdef USE_MIDI
else if (track->GetKind() == Track::Note) {
CondListAdd(track->GetStartTime(), track);
CondListAdd(track->GetEndTime(), track);
}
#endif
track = iter.Next();
}
}
// Adds to mSnapPoints, filtering by TimeConverter
void SnapManager::CondListAdd(double t, Track *tr)
{
if (mSnapToTime) {
mConverter.SetValue(t);
}
if (!mSnapToTime || mConverter.GetValue() == t) {
mSnapPoints->Add(new SnapPoint(t, tr));
}
}
SnapManager::~SnapManager()
{
int len = (int)mSnapPoints->GetCount();
int i;
for(i = 0; i < len; i++)
delete (*mSnapPoints)[i];
delete mSnapPoints;
}
// Return the time of the SnapPoint at a given index
double SnapManager::Get(int index)
{
return (*mSnapPoints)[index]->t;
}
// Returns the difference in time between t and the point at a given index
wxInt64 SnapManager::PixelDiff(double t, int index)
{
return abs(
mZoomInfo.TimeToPosition(t, 0) -
mZoomInfo.TimeToPosition(Get(index), 0)
);
}
// Find the index where this SnapPoint should go in
// sorted order, between i0 (inclusive) and i1 (exclusive).
int SnapManager::Find(double t, int i0, int i1)
{
if (i1 <= i0 + 1)
return i0;
int half = (i0 + i1) / 2;
if (t < Get(half))
return Find(t, i0, half);
else
return Find(t, half, i1);
}
// Find the SnapPoint nearest to time t
int SnapManager::Find(double t)
{
int len = (int)mSnapPoints->GetCount();
int index = Find(t, 0, len);
// At this point, either index is the closest, or the next one
// to the right is. Keep moving to the right until we get a
// different value
int next = index + 1;
while(next+1 < len && Get(next) == Get(index))
next++;
// Now return whichever one is closer to time t
if (next < len && PixelDiff(t, next) < PixelDiff(t, index))
return next;
else
return index;
}
// Helper: performs snap-to-points for Snap(). Returns true if a snap happened.
bool SnapManager::SnapToPoints(Track *currentTrack,
double t,
bool rightEdge,
double *out_t)
{
int len = (int)mSnapPoints->GetCount();
*out_t = t;
if (len == 0)
return false;
// Find the nearest SnapPoint
int index = Find(t);
// If it's too far away, just give up now
if (PixelDiff(t, index) >= mPixelTolerance)
return false;
// Otherwise, search left and right for all of the points
// within the allowed range.
int left = index;
int right = index;
int i;
while(left > 0 && PixelDiff(t, left-1) < mPixelTolerance)
left--;
while(right < len-1 && PixelDiff(t, right+1) < mPixelTolerance)
right++;
if (left == index && right == index) {
// Awesome, there's only one point that matches!
*out_t = Get(index);
return true;
}
int indexInThisTrack = -1;
int countInThisTrack = 0;
for(i=left; i<=right; i++) {
if ((*mSnapPoints)[i]->track == currentTrack) {
indexInThisTrack = i;
countInThisTrack++;
}
}
if (countInThisTrack == 1) {
// Cool, only one of the points is in the same track, so
// we'll use that one.
*out_t = Get(indexInThisTrack);
return true;
}
if (Get(right) - Get(left) < mEpsilon) {
// OK, they're basically the same point
if (rightEdge)
*out_t = Get(right); // Return rightmost
else
*out_t = Get(left); // Return leftmost
return true;
}
// None of the points matched, bummer.
return false;
}
bool SnapManager::Snap(Track *currentTrack,
double t,
bool rightEdge,
double *out_t,
bool *snappedPoint,
bool *snappedTime)
{
// First snap to points in mSnapPoints
*out_t = t;
*snappedPoint = SnapToPoints(currentTrack, t, rightEdge, out_t);
// Now snap to the time grid
*snappedTime = false;
if (mSnapToTime) {
if (*snappedPoint) {
// Since mSnapPoints only contains points on the grid, we're done
*snappedTime = true;
}
else {
// Snap time to the grid
mConverter.ValueToControls(t, GetActiveProject()->GetSnapTo() == SNAP_NEAREST);
mConverter.ControlsToValue();
*out_t = mConverter.GetValue();
*snappedTime = true;
}
}
return *snappedPoint || *snappedTime;
}
/* static */ wxArrayString SnapManager::GetSnapLabels()
{
wxArrayString labels;
labels.Add(_("Off"));
labels.Add(_("Nearest"));
labels.Add(_("Prior"));
return labels;
}
/* static */ wxArrayString SnapManager::GetSnapValues()
{
wxArrayString values;
values.Add(wxT("Off"));
values.Add(wxT("Nearest"));
values.Add(wxT("Prior"));
return values;
}
/* static */ const wxString & SnapManager::GetSnapValue(int index)
{
wxArrayString values = SnapManager::GetSnapValues();
if (index >= 0 && index < (int) values.GetCount())
{
return values[index];
}
return values[SNAP_OFF];
}
/* static */ int SnapManager::GetSnapIndex(const wxString & value)
{
wxArrayString values = SnapManager::GetSnapValues();
int index = values.Index(value);
if (index != wxNOT_FOUND)
{
return index;
}
return SNAP_OFF;
}