Chris@305
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@305
|
2
|
Chris@305
|
3 /*
|
Chris@305
|
4 Sonic Visualiser
|
Chris@305
|
5 An audio file viewer and annotation editor.
|
Chris@305
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@305
|
7 This file copyright 2006-2007 Chris Cannam and QMUL.
|
Chris@305
|
8
|
Chris@305
|
9 This program is free software; you can redistribute it and/or
|
Chris@305
|
10 modify it under the terms of the GNU General Public License as
|
Chris@305
|
11 published by the Free Software Foundation; either version 2 of the
|
Chris@305
|
12 License, or (at your option) any later version. See the file
|
Chris@305
|
13 COPYING included with this distribution for more information.
|
Chris@305
|
14 */
|
Chris@305
|
15
|
Chris@305
|
16 #ifndef _LABELLER_H_
|
Chris@305
|
17 #define _LABELLER_H_
|
Chris@305
|
18
|
Chris@305
|
19 #include "SparseModel.h"
|
Chris@305
|
20 #include "SparseValueModel.h"
|
Chris@305
|
21
|
Chris@305
|
22 #include "base/Selection.h"
|
Chris@305
|
23
|
Chris@305
|
24 #include <QObject>
|
Chris@305
|
25
|
Chris@305
|
26 #include <map>
|
Chris@305
|
27 #include <iostream>
|
Chris@305
|
28
|
Chris@305
|
29 class Labeller : public QObject
|
Chris@305
|
30 {
|
Chris@423
|
31 Q_OBJECT
|
Chris@423
|
32
|
Chris@305
|
33 public:
|
Chris@305
|
34 enum ValueType {
|
Chris@305
|
35 ValueNone,
|
Chris@305
|
36 ValueFromSimpleCounter,
|
Chris@305
|
37 ValueFromCyclicalCounter,
|
Chris@305
|
38 ValueFromTwoLevelCounter,
|
Chris@305
|
39 ValueFromFrameNumber,
|
Chris@305
|
40 ValueFromRealTime,
|
Chris@355
|
41 ValueFromDurationFromPrevious,
|
Chris@355
|
42 ValueFromDurationToNext,
|
Chris@355
|
43 ValueFromTempoFromPrevious,
|
Chris@355
|
44 ValueFromTempoToNext,
|
Chris@305
|
45 ValueFromExistingNeighbour,
|
Chris@305
|
46 ValueFromLabel
|
Chris@305
|
47 };
|
Chris@305
|
48
|
Chris@305
|
49 // uses:
|
Chris@305
|
50 //
|
Chris@305
|
51 // 1. when adding points to a time-value model, generate values
|
Chris@305
|
52 // for those points based on their times or labels or a counter
|
Chris@305
|
53 //
|
Chris@305
|
54 // 2. when adding a single point to a time-instant model, generate
|
Chris@305
|
55 // a label for it based on its time and that of the previous point
|
Chris@305
|
56 // or a counter
|
Chris@305
|
57 //
|
Chris@305
|
58 // 3. when adding a single point to a time-instant model, generate
|
Chris@305
|
59 // a label for the previous point based on its time and that of
|
Chris@305
|
60 // the point just added (as tempo is based on time to the next
|
Chris@305
|
61 // point, not the previous one)
|
Chris@305
|
62 //
|
Chris@305
|
63 // 4. re-label a set of points that have already been added to a
|
Chris@305
|
64 // model
|
Chris@305
|
65
|
Chris@355
|
66 Labeller(ValueType type = ValueNone) :
|
Chris@305
|
67 m_type(type),
|
Chris@305
|
68 m_counter(1),
|
Chris@305
|
69 m_counter2(1),
|
Chris@305
|
70 m_cycle(4),
|
Chris@305
|
71 m_dp(10),
|
Chris@305
|
72 m_rate(0) { }
|
Chris@305
|
73
|
Chris@305
|
74 Labeller(const Labeller &l) :
|
Chris@305
|
75 QObject(),
|
Chris@305
|
76 m_type(l.m_type),
|
Chris@380
|
77 m_counter(l.m_counter),
|
Chris@380
|
78 m_counter2(l.m_counter2),
|
Chris@305
|
79 m_cycle(l.m_cycle),
|
Chris@305
|
80 m_dp(l.m_dp),
|
Chris@305
|
81 m_rate(l.m_rate) { }
|
Chris@305
|
82
|
Chris@305
|
83 virtual ~Labeller() { }
|
Chris@305
|
84
|
Chris@305
|
85 typedef std::map<ValueType, QString> TypeNameMap;
|
Chris@305
|
86 TypeNameMap getTypeNames() const {
|
Chris@305
|
87 TypeNameMap m;
|
Chris@355
|
88 m[ValueNone]
|
Chris@355
|
89 = tr("No numbering");
|
Chris@355
|
90 m[ValueFromSimpleCounter]
|
Chris@355
|
91 = tr("Simple counter");
|
Chris@355
|
92 m[ValueFromCyclicalCounter]
|
Chris@355
|
93 = tr("Cyclical counter");
|
Chris@355
|
94 m[ValueFromTwoLevelCounter]
|
Chris@355
|
95 = tr("Cyclical two-level counter (bar/beat)");
|
Chris@355
|
96 m[ValueFromFrameNumber]
|
Chris@355
|
97 = tr("Audio sample frame number");
|
Chris@355
|
98 m[ValueFromRealTime]
|
Chris@355
|
99 = tr("Time in seconds");
|
Chris@355
|
100 m[ValueFromDurationToNext]
|
Chris@355
|
101 = tr("Duration to the following item");
|
Chris@355
|
102 m[ValueFromTempoToNext]
|
Chris@355
|
103 = tr("Tempo (bpm) based on duration to following item");
|
Chris@355
|
104 m[ValueFromDurationFromPrevious]
|
Chris@355
|
105 = tr("Duration since the previous item");
|
Chris@355
|
106 m[ValueFromTempoFromPrevious]
|
Chris@355
|
107 = tr("Tempo (bpm) based on duration since previous item");
|
Chris@355
|
108 m[ValueFromExistingNeighbour]
|
Chris@355
|
109 = tr("Same as the nearest previous item");
|
Chris@355
|
110 m[ValueFromLabel]
|
Chris@355
|
111 = tr("Value extracted from the item's label (where possible)");
|
Chris@305
|
112 return m;
|
Chris@305
|
113 }
|
Chris@305
|
114
|
Chris@305
|
115 ValueType getType() const { return m_type; }
|
Chris@305
|
116 void setType(ValueType type) { m_type = type; }
|
Chris@305
|
117
|
Chris@305
|
118 int getCounterValue() const { return m_counter; }
|
Chris@305
|
119 void setCounterValue(int v) { m_counter = v; }
|
Chris@305
|
120
|
Chris@305
|
121 int getSecondLevelCounterValue() const { return m_counter2; }
|
Chris@305
|
122 void setSecondLevelCounterValue(int v) { m_counter2 = v; }
|
Chris@305
|
123
|
Chris@305
|
124 int getCounterCycleSize() const { return m_cycle; }
|
Chris@305
|
125 void setCounterCycleSize(int s) {
|
Chris@305
|
126 m_cycle = s;
|
Chris@305
|
127 m_dp = 1;
|
Chris@305
|
128 while (s > 0) {
|
Chris@305
|
129 s /= 10;
|
Chris@305
|
130 m_dp *= 10;
|
Chris@305
|
131 }
|
Chris@307
|
132 if (m_counter > m_cycle) m_counter = 1;
|
Chris@305
|
133 }
|
Chris@305
|
134
|
Chris@1046
|
135 void setSampleRate(sv_samplerate_t rate) { m_rate = rate; }
|
Chris@305
|
136
|
Chris@830
|
137 void resetCounters() {
|
Chris@830
|
138 m_counter = 1;
|
Chris@830
|
139 m_counter2 = 1;
|
Chris@830
|
140 m_cycle = 4;
|
Chris@830
|
141 }
|
Chris@830
|
142
|
Chris@305
|
143 void incrementCounter() {
|
Chris@305
|
144 m_counter++;
|
Chris@305
|
145 if (m_type == ValueFromCyclicalCounter ||
|
Chris@305
|
146 m_type == ValueFromTwoLevelCounter) {
|
Chris@305
|
147 if (m_counter > m_cycle) {
|
Chris@305
|
148 m_counter = 1;
|
Chris@305
|
149 m_counter2++;
|
Chris@305
|
150 }
|
Chris@305
|
151 }
|
Chris@305
|
152 }
|
Chris@305
|
153
|
Chris@305
|
154 template <typename PointType>
|
Chris@305
|
155 void label(PointType &newPoint, PointType *prevPoint = 0) {
|
Chris@305
|
156 if (m_type == ValueNone) {
|
Chris@305
|
157 newPoint.label = "";
|
Chris@305
|
158 } else if (m_type == ValueFromTwoLevelCounter) {
|
Chris@305
|
159 newPoint.label = tr("%1.%2").arg(m_counter2).arg(m_counter);
|
Chris@305
|
160 incrementCounter();
|
Chris@306
|
161 } else if (m_type == ValueFromFrameNumber) {
|
Chris@306
|
162 // avoid going through floating-point value
|
Chris@306
|
163 newPoint.label = tr("%1").arg(newPoint.frame);
|
Chris@305
|
164 } else {
|
Chris@305
|
165 float value = getValueFor<PointType>(newPoint, prevPoint);
|
Chris@305
|
166 if (actingOnPrevPoint() && prevPoint) {
|
Chris@305
|
167 prevPoint->label = QString("%1").arg(value);
|
Chris@305
|
168 } else {
|
Chris@305
|
169 newPoint.label = QString("%1").arg(value);
|
Chris@305
|
170 }
|
Chris@305
|
171 }
|
Chris@305
|
172 }
|
Chris@305
|
173
|
Chris@1291
|
174 /**
|
Chris@1291
|
175 * Relabel all points in the given model that lie within the given
|
Chris@1291
|
176 * multi-selection, according to the labelling properties of this
|
Chris@1291
|
177 * labeller. Return a command that has been executed but not yet
|
Chris@1291
|
178 * added to the history.
|
Chris@1291
|
179 */
|
Chris@305
|
180 template <typename PointType>
|
Chris@1291
|
181 Command *labelAll(SparseModel<PointType> &model, MultiSelection *ms) {
|
Chris@305
|
182
|
Chris@305
|
183 typename SparseModel<PointType>::PointList::iterator i;
|
Chris@305
|
184 typename SparseModel<PointType>::PointList pl(model.getPoints());
|
Chris@305
|
185
|
Chris@305
|
186 typename SparseModel<PointType>::EditCommand *command =
|
Chris@305
|
187 new typename SparseModel<PointType>::EditCommand
|
Chris@305
|
188 (&model, tr("Label Points"));
|
Chris@305
|
189
|
Chris@305
|
190 PointType prevPoint(0);
|
Chris@305
|
191
|
Chris@305
|
192 for (i = pl.begin(); i != pl.end(); ++i) {
|
Chris@305
|
193
|
Chris@305
|
194 bool inRange = true;
|
Chris@305
|
195 if (ms) {
|
Chris@305
|
196 Selection s(ms->getContainingSelection(i->frame, false));
|
Chris@305
|
197 if (s.isEmpty() || !s.contains(i->frame)) {
|
Chris@305
|
198 inRange = false;
|
Chris@305
|
199 }
|
Chris@305
|
200 }
|
Chris@305
|
201
|
Chris@305
|
202 PointType p(*i);
|
Chris@305
|
203
|
Chris@305
|
204 if (!inRange) {
|
Chris@305
|
205 prevPoint = p;
|
Chris@305
|
206 continue;
|
Chris@305
|
207 }
|
Chris@305
|
208
|
Chris@305
|
209 if (actingOnPrevPoint()) {
|
Chris@305
|
210 if (i != pl.begin()) {
|
Chris@305
|
211 command->deletePoint(prevPoint);
|
Chris@305
|
212 label<PointType>(p, &prevPoint);
|
Chris@305
|
213 command->addPoint(prevPoint);
|
Chris@305
|
214 }
|
Chris@305
|
215 } else {
|
Chris@305
|
216 command->deletePoint(p);
|
Chris@305
|
217 label<PointType>(p, &prevPoint);
|
Chris@305
|
218 command->addPoint(p);
|
Chris@305
|
219 }
|
Chris@305
|
220
|
Chris@305
|
221 prevPoint = p;
|
Chris@305
|
222 }
|
Chris@305
|
223
|
Chris@1291
|
224 return command->finish();
|
Chris@1291
|
225 }
|
Chris@1291
|
226
|
Chris@1291
|
227 /**
|
Chris@1291
|
228 * For each point in the given model (except the last), if that
|
Chris@1291
|
229 * point lies within the given multi-selection, add n-1 new points
|
Chris@1291
|
230 * at equally spaced intervals between it and the following point.
|
Chris@1291
|
231 * Return a command that has been executed but not yet added to
|
Chris@1291
|
232 * the history.
|
Chris@1291
|
233 */
|
Chris@1291
|
234 template <typename PointType>
|
Chris@1291
|
235 Command *subdivide(SparseModel<PointType> &model, MultiSelection *ms, int n) {
|
Chris@1291
|
236
|
Chris@1291
|
237 typename SparseModel<PointType>::PointList::iterator i;
|
Chris@1291
|
238 typename SparseModel<PointType>::PointList pl(model.getPoints());
|
Chris@1291
|
239
|
Chris@1291
|
240 typename SparseModel<PointType>::EditCommand *command =
|
Chris@1291
|
241 new typename SparseModel<PointType>::EditCommand
|
Chris@1291
|
242 (&model, tr("Subdivide"));
|
Chris@1291
|
243
|
Chris@1291
|
244 for (i = pl.begin(); i != pl.end(); ++i) {
|
Chris@1291
|
245
|
Chris@1291
|
246 auto j = i;
|
Chris@1291
|
247 // require a "next point" even if it's not in selection
|
Chris@1291
|
248 if (++j == pl.end()) {
|
Chris@1291
|
249 break;
|
Chris@1291
|
250 }
|
Chris@1291
|
251
|
Chris@1291
|
252 bool inRange = true;
|
Chris@1291
|
253 if (ms) {
|
Chris@1291
|
254 Selection s(ms->getContainingSelection(i->frame, false));
|
Chris@1291
|
255 if (s.isEmpty() || !s.contains(i->frame)) {
|
Chris@1291
|
256 inRange = false;
|
Chris@1291
|
257 }
|
Chris@1291
|
258 }
|
Chris@1291
|
259 if (!inRange) {
|
Chris@1291
|
260 continue;
|
Chris@1291
|
261 }
|
Chris@1291
|
262
|
Chris@1291
|
263 PointType p(*i);
|
Chris@1291
|
264 PointType nextP(*j);
|
Chris@1291
|
265
|
Chris@1291
|
266 // n is the number of subdivisions, so we add n-1 new
|
Chris@1291
|
267 // points equally spaced between p and nextP
|
Chris@1291
|
268
|
Chris@1291
|
269 for (int m = 1; m < n; ++m) {
|
Chris@1291
|
270 sv_frame_t f = p.frame + (m * (nextP.frame - p.frame)) / n;
|
Chris@1291
|
271 PointType newPoint(p);
|
Chris@1291
|
272 newPoint.frame = f;
|
Chris@1291
|
273 newPoint.label = tr("%1.%2").arg(p.label).arg(m+1);
|
Chris@1291
|
274 command->addPoint(newPoint);
|
Chris@1291
|
275 }
|
Chris@1291
|
276 }
|
Chris@1291
|
277
|
Chris@1291
|
278 return command->finish();
|
Chris@305
|
279 }
|
Chris@305
|
280
|
Chris@1292
|
281 /**
|
Chris@1292
|
282 * Return a command that has been executed but not yet added to
|
Chris@1292
|
283 * the history.
|
Chris@1292
|
284 */
|
Chris@1292
|
285 template <typename PointType>
|
Chris@1292
|
286 Command *winnow(SparseModel<PointType> &model, MultiSelection *ms, int n) {
|
Chris@1292
|
287
|
Chris@1292
|
288 typename SparseModel<PointType>::PointList::iterator i;
|
Chris@1292
|
289 typename SparseModel<PointType>::PointList pl(model.getPoints());
|
Chris@1292
|
290
|
Chris@1292
|
291 typename SparseModel<PointType>::EditCommand *command =
|
Chris@1292
|
292 new typename SparseModel<PointType>::EditCommand
|
Chris@1292
|
293 (&model, tr("Subdivide"));
|
Chris@1292
|
294
|
Chris@1292
|
295 int counter = 0;
|
Chris@1292
|
296
|
Chris@1292
|
297 for (i = pl.begin(); i != pl.end(); ++i) {
|
Chris@1292
|
298
|
Chris@1292
|
299 bool inRange = true;
|
Chris@1292
|
300 if (ms) {
|
Chris@1292
|
301 Selection s(ms->getContainingSelection(i->frame, false));
|
Chris@1292
|
302 if (s.isEmpty() || !s.contains(i->frame)) {
|
Chris@1292
|
303 inRange = false;
|
Chris@1292
|
304 }
|
Chris@1292
|
305 }
|
Chris@1292
|
306 if (!inRange) {
|
Chris@1292
|
307 counter = 0;
|
Chris@1292
|
308 continue;
|
Chris@1292
|
309 }
|
Chris@1292
|
310
|
Chris@1292
|
311 ++counter;
|
Chris@1292
|
312
|
Chris@1292
|
313 if (counter == n+1) counter = 1;
|
Chris@1292
|
314 if (counter == 1) {
|
Chris@1292
|
315 // this is an Nth instant, don't remove it
|
Chris@1292
|
316 continue;
|
Chris@1292
|
317 }
|
Chris@1292
|
318
|
Chris@1292
|
319 command->deletePoint(*i);
|
Chris@1292
|
320 }
|
Chris@1292
|
321
|
Chris@1292
|
322 return command->finish();
|
Chris@1292
|
323 }
|
Chris@1292
|
324
|
Chris@305
|
325 template <typename PointType>
|
Chris@305
|
326 void setValue(PointType &newPoint, PointType *prevPoint = 0) {
|
Chris@305
|
327 if (m_type == ValueFromExistingNeighbour) {
|
Chris@305
|
328 if (!prevPoint) {
|
Chris@305
|
329 std::cerr << "ERROR: Labeller::setValue: Previous point required but not provided" << std::endl;
|
Chris@305
|
330 } else {
|
Chris@305
|
331 newPoint.value = prevPoint->value;
|
Chris@305
|
332 }
|
Chris@305
|
333 } else {
|
Chris@305
|
334 float value = getValueFor<PointType>(newPoint, prevPoint);
|
Chris@305
|
335 if (actingOnPrevPoint() && prevPoint) {
|
Chris@305
|
336 prevPoint->value = value;
|
Chris@305
|
337 } else {
|
Chris@305
|
338 newPoint.value = value;
|
Chris@305
|
339 }
|
Chris@305
|
340 }
|
Chris@305
|
341 }
|
Chris@305
|
342
|
Chris@355
|
343 bool requiresPrevPoint() const {
|
Chris@355
|
344 return (m_type == ValueFromDurationFromPrevious ||
|
Chris@355
|
345 m_type == ValueFromDurationToNext ||
|
Chris@355
|
346 m_type == ValueFromTempoFromPrevious ||
|
Chris@355
|
347 m_type == ValueFromDurationToNext);
|
Chris@355
|
348 }
|
Chris@355
|
349
|
Chris@305
|
350 bool actingOnPrevPoint() const {
|
Chris@355
|
351 return (m_type == ValueFromDurationToNext ||
|
Chris@355
|
352 m_type == ValueFromTempoToNext);
|
Chris@305
|
353 }
|
Chris@305
|
354
|
Chris@305
|
355 protected:
|
Chris@305
|
356 template <typename PointType>
|
Chris@305
|
357 float getValueFor(PointType &newPoint, PointType *prevPoint)
|
Chris@305
|
358 {
|
Chris@305
|
359 float value = 0.f;
|
Chris@305
|
360
|
Chris@305
|
361 switch (m_type) {
|
Chris@305
|
362
|
Chris@305
|
363 case ValueNone:
|
Chris@305
|
364 value = 0;
|
Chris@305
|
365 break;
|
Chris@305
|
366
|
Chris@305
|
367 case ValueFromSimpleCounter:
|
Chris@305
|
368 case ValueFromCyclicalCounter:
|
Chris@1046
|
369 value = float(m_counter);
|
Chris@305
|
370 incrementCounter();
|
Chris@305
|
371 break;
|
Chris@305
|
372
|
Chris@305
|
373 case ValueFromTwoLevelCounter:
|
Chris@1046
|
374 value = float(m_counter2 + double(m_counter) / double(m_dp));
|
Chris@305
|
375 incrementCounter();
|
Chris@305
|
376 break;
|
Chris@305
|
377
|
Chris@305
|
378 case ValueFromFrameNumber:
|
Chris@1046
|
379 value = float(newPoint.frame);
|
Chris@305
|
380 break;
|
Chris@305
|
381
|
Chris@305
|
382 case ValueFromRealTime:
|
Chris@1046
|
383 if (m_rate == 0.0) {
|
Chris@305
|
384 std::cerr << "ERROR: Labeller::getValueFor: Real-time conversion required, but no sample rate set" << std::endl;
|
Chris@305
|
385 } else {
|
Chris@1046
|
386 value = float(double(newPoint.frame) / m_rate);
|
Chris@305
|
387 }
|
Chris@305
|
388 break;
|
Chris@305
|
389
|
Chris@355
|
390 case ValueFromDurationToNext:
|
Chris@355
|
391 case ValueFromTempoToNext:
|
Chris@355
|
392 case ValueFromDurationFromPrevious:
|
Chris@355
|
393 case ValueFromTempoFromPrevious:
|
Chris@1046
|
394 if (m_rate == 0.0) {
|
Chris@305
|
395 std::cerr << "ERROR: Labeller::getValueFor: Real-time conversion required, but no sample rate set" << std::endl;
|
Chris@305
|
396 } else if (!prevPoint) {
|
Chris@305
|
397 std::cerr << "ERROR: Labeller::getValueFor: Time difference required, but only one point provided" << std::endl;
|
Chris@305
|
398 } else {
|
Chris@1046
|
399 sv_frame_t f0 = prevPoint->frame, f1 = newPoint.frame;
|
Chris@355
|
400 if (m_type == ValueFromDurationToNext ||
|
Chris@355
|
401 m_type == ValueFromDurationFromPrevious) {
|
Chris@1046
|
402 value = float(double(f1 - f0) / m_rate);
|
Chris@305
|
403 } else {
|
Chris@305
|
404 if (f1 > f0) {
|
Chris@1046
|
405 value = float((60.0 * m_rate) / double(f1 - f0));
|
Chris@305
|
406 }
|
Chris@305
|
407 }
|
Chris@305
|
408 }
|
Chris@305
|
409 break;
|
Chris@305
|
410
|
Chris@305
|
411 case ValueFromExistingNeighbour:
|
Chris@305
|
412 // need to deal with this in the calling function, as this
|
Chris@305
|
413 // function must handle points that don't have values to
|
Chris@305
|
414 // read from
|
Chris@305
|
415 break;
|
Chris@305
|
416
|
Chris@305
|
417 case ValueFromLabel:
|
Chris@305
|
418 if (newPoint.label != "") {
|
Chris@305
|
419 // more forgiving than QString::toFloat()
|
Chris@1046
|
420 value = float(atof(newPoint.label.toLocal8Bit()));
|
Chris@305
|
421 } else {
|
Chris@305
|
422 value = 0.f;
|
Chris@305
|
423 }
|
Chris@305
|
424 break;
|
Chris@305
|
425 }
|
Chris@305
|
426
|
Chris@305
|
427 return value;
|
Chris@305
|
428 }
|
Chris@305
|
429
|
Chris@305
|
430 ValueType m_type;
|
Chris@305
|
431 int m_counter;
|
Chris@305
|
432 int m_counter2;
|
Chris@305
|
433 int m_cycle;
|
Chris@305
|
434 int m_dp;
|
Chris@1046
|
435 sv_samplerate_t m_rate;
|
Chris@305
|
436 };
|
Chris@305
|
437
|
Chris@305
|
438 #endif
|