forked from Sneeds-Feed-and-Seed/sneedacity
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSnap.cpp
335 lines (278 loc) · 7.61 KB
/
Snap.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
/**********************************************************************
Sneedacity: A Digital Audio Editor
Snap.cpp
Dominic Mazzoni
**********************************************************************/
#include "Snap.h"
#include <algorithm>
#include <cstdlib>
#include "Project.h"
#include "ProjectSettings.h"
#include "Track.h"
#include "ViewInfo.h"
inline bool operator < (SnapPoint s1, SnapPoint s2)
{
return s1.t < s2.t;
}
SnapManager::SnapManager(const SneedacityProject &project,
SnapPointArray candidates,
const ZoomInfo &zoomInfo,
bool noTimeSnap,
int pixelTolerance)
: mProject{ &project }
, mZoomInfo{ &zoomInfo }
, mPixelTolerance{ pixelTolerance }
, mNoTimeSnap{ noTimeSnap }
, mCandidates{ std::move( candidates ) }
, mSnapPoints{}
, mConverter{ NumericConverter::TIME }
{
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 SneedacityProject &project,
const TrackList &tracks,
const ZoomInfo &zoomInfo,
bool noTimeSnap,
int pixelTolerance)
: SnapManager{ project,
FindCandidates( tracks ),
zoomInfo, noTimeSnap, pixelTolerance }
{
}
SnapManager::~SnapManager()
{
}
void SnapManager::Reinit()
{
const auto &settings = ProjectSettings::Get( *mProject );
int snapTo = settings.GetSnapTo();
double rate = settings.GetRate();
auto format = settings.GetSelectionFormat();
// No need to reinit if these are still the same
if (snapTo == mSnapTo && rate == mRate && format == mFormat)
{
return;
}
// Save NEW settings
mSnapTo = snapTo;
mRate = rate;
mFormat = format;
mSnapPoints.clear();
// Grab time-snapping prefs (unless otherwise requested)
mSnapToTime = false;
// Look up the format string
if (mSnapTo != SNAP_OFF && !mNoTimeSnap)
{
mSnapToTime = true;
mConverter.SetSampleRate(mRate);
mConverter.SetFormatName(mFormat);
}
// Add a SnapPoint at t=0
mSnapPoints.push_back(SnapPoint{});
// 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());
}
// Adds to mSnapPoints, filtering by TimeConverter
void SnapManager::CondListAdd(double t, const Track *track)
{
if (mSnapToTime)
{
mConverter.SetValue(t);
}
if (!mSnapToTime || mConverter.GetValue() == t)
{
mSnapPoints.push_back(SnapPoint{ t, track });
}
}
// Return the time of the SnapPoint at a given index
double SnapManager::Get(size_t index)
{
return mSnapPoints[index].t;
}
// Returns the difference in time between t and the point at a given index
wxInt64 SnapManager::PixelDiff(double t, size_t index)
{
return std::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).
size_t SnapManager::Find(double t, size_t i0, size_t i1)
{
if (i1 <= i0 + 1)
{
return i0;
}
size_t half = (i0 + i1) / 2;
if (t < Get(half))
{
return Find(t, i0, half);
}
return Find(t, half, i1);
}
// Find the SnapPoint nearest to time t
size_t SnapManager::Find(double t)
{
size_t cnt = mSnapPoints.size();
size_t index = Find(t, 0, cnt);
// 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
size_t next = index + 1;
while (next + 1 < cnt && Get(next) == Get(index))
{
next++;
}
// Now return whichever one is closer to time t
if (next < cnt && PixelDiff(t, next) < PixelDiff(t, index))
{
return next;
}
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 *outT)
{
*outT = t;
size_t cnt = mSnapPoints.size();
if (cnt == 0)
{
return false;
}
// Find the nearest SnapPoint
size_t 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.
size_t left = index;
size_t right = index;
size_t i;
while (left > 0 && PixelDiff(t, left - 1) < mPixelTolerance)
{
left--;
}
while (right < cnt - 1 && PixelDiff(t, right + 1) < mPixelTolerance)
{
right++;
}
if (left == index && right == index)
{
// Awesome, there's only one point that matches!
*outT = Get(index);
return true;
}
size_t indexInThisTrack = 0;
size_t 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.
*outT = Get(indexInThisTrack);
return true;
}
if (Get(right) - Get(left) < mEpsilon)
{
// OK, they're basically the same point
if (rightEdge)
{
*outT = Get(right); // Return rightmost
}
else {
*outT = Get(left); // Return leftmost
}
return true;
}
// None of the points matched, bummer.
return false;
}
SnapResults SnapManager::Snap
(Track *currentTrack, double t, bool rightEdge)
{
SnapResults results;
// Check to see if we need to reinitialize
Reinit();
results.timeSnappedTime = results.outTime = t;
results.outCoord = mZoomInfo->TimeToPosition(t);
// First snap to points in mSnapPoints
results.snappedPoint =
SnapToPoints(currentTrack, t, rightEdge, &results.outTime);
if (mSnapToTime) {
// Find where it would snap time to the grid
mConverter.ValueToControls(
t,
ProjectSettings::Get( *mProject ).GetSnapTo() == SNAP_NEAREST
);
mConverter.ControlsToValue();
results.timeSnappedTime = mConverter.GetValue();
}
results.snappedTime = false;
if (mSnapToTime)
{
if (results.snappedPoint)
{
// Since mSnapPoints only contains points on the grid, we're done
results.snappedTime = true;
}
else
{
results.outTime = results.timeSnappedTime;
results.snappedTime = true;
}
}
if (results.Snapped())
results.outCoord = mZoomInfo->TimeToPosition(results.outTime);
return results;
}
/* static */ const TranslatableStrings &SnapManager::GetSnapLabels()
{
static const TranslatableStrings result{
XO("Off") ,
XO("Nearest") ,
XO("Prior") ,
};
return result;
}
#include "AColor.h"
void SnapManager::Draw( wxDC *dc, wxInt64 snap0, wxInt64 snap1 )
{
AColor::SnapGuidePen(dc);
if ( snap0 >= 0 ) {
AColor::Line(*dc, (int)snap0, 0, (int)snap0, 30000);
}
if ( snap1 >= 0 ) {
AColor::Line(*dc, (int)snap1, 0, (int)snap1, 30000);
}
}