Mercurial > hg > svcore
comparison data/model/Labeller.h @ 1652:08bed13d3a26 single-point
Update Labeller to new event API + fewer magical side-effects
author | Chris Cannam |
---|---|
date | Tue, 19 Mar 2019 13:05:56 +0000 |
parents | ad5f892c0c4d |
children | 9d82b164f264 |
comparison
equal
deleted
inserted
replaced
1651:7a56bb85030f | 1652:08bed13d3a26 |
---|---|
14 */ | 14 */ |
15 | 15 |
16 #ifndef SV_LABELLER_H | 16 #ifndef SV_LABELLER_H |
17 #define SV_LABELLER_H | 17 #define SV_LABELLER_H |
18 | 18 |
19 #include "SparseModel.h" | |
20 #include "SparseValueModel.h" | |
21 | |
22 #include "base/Selection.h" | 19 #include "base/Selection.h" |
20 #include "base/Event.h" | |
21 | |
22 #include "EventCommands.h" | |
23 | 23 |
24 #include <QObject> | 24 #include <QObject> |
25 | 25 |
26 #include <map> | 26 #include <map> |
27 #include <iostream> | 27 #include <iostream> |
60 // the point just added (as tempo is based on time to the next | 60 // the point just added (as tempo is based on time to the next |
61 // point, not the previous one) | 61 // point, not the previous one) |
62 // | 62 // |
63 // 4. re-label a set of points that have already been added to a | 63 // 4. re-label a set of points that have already been added to a |
64 // model | 64 // model |
65 // | |
66 // 5. generate new labelled points in the gaps between other | |
67 // points (subdivide), or remove them (winnow) | |
65 | 68 |
66 Labeller(ValueType type = ValueNone) : | 69 Labeller(ValueType type = ValueNone) : |
67 m_type(type), | 70 m_type(type), |
68 m_counter(1), | 71 m_counter(1), |
69 m_counter2(1), | 72 m_counter2(1), |
149 m_counter2++; | 152 m_counter2++; |
150 } | 153 } |
151 } | 154 } |
152 } | 155 } |
153 | 156 |
154 template <typename PointType> | 157 enum Application { |
155 void label(PointType &newPoint, PointType *prevPoint = 0) { | 158 AppliesToThisEvent, |
159 AppliesToPreviousEvent | |
160 }; | |
161 typedef std::pair<Application, Event> Relabelling; | |
162 typedef std::pair<Application, Event> Revaluing; | |
163 | |
164 /** | |
165 * Return a labelled event based on the given event, previous | |
166 * event if supplied, and internal labeller state. The returned | |
167 * event replaces either the event provided or the previous event, | |
168 * depending on the Application value in the returned pair. | |
169 */ | |
170 Relabelling | |
171 label(Event e, const Event *prev = nullptr) { | |
172 | |
173 QString label = e.getLabel(); | |
174 | |
156 if (m_type == ValueNone) { | 175 if (m_type == ValueNone) { |
157 newPoint.label = ""; | 176 label = ""; |
158 } else if (m_type == ValueFromTwoLevelCounter) { | 177 } else if (m_type == ValueFromTwoLevelCounter) { |
159 newPoint.label = tr("%1.%2").arg(m_counter2).arg(m_counter); | 178 label = tr("%1.%2").arg(m_counter2).arg(m_counter); |
160 incrementCounter(); | 179 incrementCounter(); |
161 } else if (m_type == ValueFromFrameNumber) { | 180 } else if (m_type == ValueFromFrameNumber) { |
162 // avoid going through floating-point value | 181 // avoid going through floating-point value |
163 newPoint.label = tr("%1").arg(newPoint.frame); | 182 label = tr("%1").arg(e.getFrame()); |
164 } else { | 183 } else { |
165 float value = getValueFor<PointType>(newPoint, prevPoint); | 184 float value = getValueFor(e, prev); |
166 if (actingOnPrevPoint() && prevPoint) { | 185 label = QString("%1").arg(value); |
167 prevPoint->label = QString("%1").arg(value); | 186 } |
187 | |
188 if (actingOnPrevEvent() && prev) { | |
189 return { AppliesToPreviousEvent, prev->withLabel(label) }; | |
190 } else { | |
191 return { AppliesToThisEvent, e.withLabel(label) }; | |
192 } | |
193 } | |
194 | |
195 /** | |
196 * Return an event with a value following the labelling scheme, | |
197 * based on the given event, previous event if supplied, and | |
198 * internal labeller state. The returned event replaces either the | |
199 * event provided or the previous event, depending on the | |
200 * Application value in the returned pair. | |
201 */ | |
202 Revaluing | |
203 revalue(Event e, const Event *prev = nullptr) { | |
204 | |
205 float value = e.getValue(); | |
206 | |
207 if (m_type == ValueFromExistingNeighbour) { | |
208 if (!prev) { | |
209 std::cerr << "ERROR: Labeller::setValue: Previous point required but not provided" << std::endl; | |
168 } else { | 210 } else { |
169 newPoint.label = QString("%1").arg(value); | 211 return { AppliesToThisEvent, e.withValue(prev->getValue()) }; |
170 } | 212 } |
171 } | 213 } else { |
172 } | 214 value = getValueFor(e, prev); |
173 | 215 } |
216 | |
217 if (actingOnPrevEvent() && prev) { | |
218 return { AppliesToPreviousEvent, prev->withValue(value) }; | |
219 } else { | |
220 return { AppliesToThisEvent, e.withValue(value) }; | |
221 } | |
222 } | |
223 | |
174 /** | 224 /** |
175 * Relabel all points in the given model that lie within the given | 225 * Relabel all events in the given event vector that lie within |
176 * multi-selection, according to the labelling properties of this | 226 * the given multi-selection, according to the labelling |
177 * labeller. Return a command that has been executed but not yet | 227 * properties of this labeller. Return a command that has been |
228 * executed but not yet added to the history. | |
229 */ | |
230 Command *labelAll(EventEditable *editable, | |
231 MultiSelection *ms, | |
232 const EventVector &allEvents) { | |
233 | |
234 ChangeEventsCommand *command = new ChangeEventsCommand | |
235 (editable, tr("Label Points")); | |
236 | |
237 Event prev; | |
238 bool havePrev = false; | |
239 | |
240 for (auto p: allEvents) { | |
241 | |
242 if (ms) { | |
243 Selection s(ms->getContainingSelection(p.getFrame(), false)); | |
244 if (!s.contains(p.getFrame())) { | |
245 prev = p; | |
246 havePrev = true; | |
247 continue; | |
248 } | |
249 } | |
250 | |
251 auto labelling = label(p, havePrev ? &prev : nullptr); | |
252 | |
253 if (labelling.first == AppliesToThisEvent) { | |
254 command->remove(p); | |
255 } else { | |
256 command->remove(prev); | |
257 } | |
258 | |
259 command->add(labelling.second); | |
260 | |
261 prev = p; | |
262 havePrev = true; | |
263 } | |
264 | |
265 return command->finish(); | |
266 } | |
267 | |
268 /** | |
269 * For each event in the given event vector (except the last), if | |
270 * that event lies within the given multi-selection, add n-1 new | |
271 * events at equally spaced intervals between it and the following | |
272 * event. Return a command that has been executed but not yet | |
178 * added to the history. | 273 * added to the history. |
179 */ | 274 */ |
180 template <typename PointType> | 275 Command *subdivide(EventEditable *editable, |
181 Command *labelAll(SparseModel<PointType> &model, MultiSelection *ms) { | 276 MultiSelection *ms, |
182 | 277 const EventVector &allEvents, |
183 auto points(model.getPoints()); | 278 int n) { |
184 auto command = new typename SparseModel<PointType>::EditCommand | 279 |
185 (&model, tr("Label Points")); | 280 ChangeEventsCommand *command = new ChangeEventsCommand |
186 | 281 (editable, tr("Subdivide Points")); |
187 PointType prevPoint(0); | 282 |
188 bool havePrevPoint(false); | 283 for (auto i = allEvents.begin(); i != allEvents.end(); ++i) { |
189 | 284 |
190 for (auto p: points) { | 285 auto j = i; |
286 // require a "next point" even if it's not in selection | |
287 if (++j == allEvents.end()) { | |
288 break; | |
289 } | |
191 | 290 |
192 if (ms) { | 291 if (ms) { |
193 Selection s(ms->getContainingSelection(p.frame, false)); | 292 Selection s(ms->getContainingSelection(i->getFrame(), false)); |
194 if (!s.contains(p.frame)) { | 293 if (!s.contains(i->getFrame())) { |
195 prevPoint = p; | |
196 havePrevPoint = true; | |
197 continue; | 294 continue; |
198 } | 295 } |
199 } | 296 } |
200 | 297 |
201 if (actingOnPrevPoint()) { | 298 Event p(*i); |
202 if (havePrevPoint) { | 299 Event nextP(*j); |
203 command->deletePoint(prevPoint); | 300 |
204 label<PointType>(p, &prevPoint); | 301 // n is the number of subdivisions, so we add n-1 new |
205 command->addPoint(prevPoint); | 302 // points equally spaced between p and nextP |
206 } | 303 |
207 } else { | 304 for (int m = 1; m < n; ++m) { |
208 command->deletePoint(p); | 305 sv_frame_t f = p.getFrame() + |
209 label<PointType>(p, &prevPoint); | 306 (m * (nextP.getFrame() - p.getFrame())) / n; |
210 command->addPoint(p); | 307 Event newPoint = p |
211 } | 308 .withFrame(f) |
212 | 309 .withLabel(tr("%1.%2").arg(p.getLabel()).arg(m+1)); |
213 prevPoint = p; | 310 command->add(newPoint); |
214 havePrevPoint = true; | 311 } |
215 } | 312 } |
216 | 313 |
217 return command->finish(); | 314 return command->finish(); |
218 } | 315 } |
219 | 316 |
220 /** | 317 /** |
221 * For each point in the given model (except the last), if that | 318 * The opposite of subdivide. Given an event vector, a |
222 * point lies within the given multi-selection, add n-1 new points | 319 * multi-selection, and a number n, remove all but every nth event |
223 * at equally spaced intervals between it and the following point. | 320 * from the vector within the extents of the multi-selection. |
224 * Return a command that has been executed but not yet added to | 321 * Return a command that has been executed but not yet added to |
225 * the history. | 322 * the history. |
226 */ | 323 */ |
227 template <typename PointType> | 324 Command *winnow(EventEditable *editable, |
228 Command *subdivide(SparseModel<PointType> &model, MultiSelection *ms, int n) { | 325 MultiSelection *ms, |
229 | 326 const EventVector &allEvents, |
230 auto points(model.getPoints()); | 327 int n) { |
231 auto command = new typename SparseModel<PointType>::EditCommand | 328 |
232 (&model, tr("Subdivide Points")); | 329 ChangeEventsCommand *command = new ChangeEventsCommand |
233 | 330 (editable, tr("Winnow Points")); |
234 for (auto i = points.begin(); i != points.end(); ++i) { | |
235 | |
236 auto j = i; | |
237 // require a "next point" even if it's not in selection | |
238 if (++j == points.end()) { | |
239 break; | |
240 } | |
241 | |
242 if (ms) { | |
243 Selection s(ms->getContainingSelection(i->frame, false)); | |
244 if (!s.contains(i->frame)) { | |
245 continue; | |
246 } | |
247 } | |
248 | |
249 PointType p(*i); | |
250 PointType nextP(*j); | |
251 | |
252 // n is the number of subdivisions, so we add n-1 new | |
253 // points equally spaced between p and nextP | |
254 | |
255 for (int m = 1; m < n; ++m) { | |
256 sv_frame_t f = p.frame + (m * (nextP.frame - p.frame)) / n; | |
257 PointType newPoint(p); | |
258 newPoint.frame = f; | |
259 newPoint.label = tr("%1.%2").arg(p.label).arg(m+1); | |
260 command->addPoint(newPoint); | |
261 } | |
262 } | |
263 | |
264 return command->finish(); | |
265 } | |
266 | |
267 /** | |
268 * Return a command that has been executed but not yet added to | |
269 * the history. | |
270 */ | |
271 template <typename PointType> | |
272 Command *winnow(SparseModel<PointType> &model, MultiSelection *ms, int n) { | |
273 | |
274 auto points(model.getPoints()); | |
275 auto command = new typename SparseModel<PointType>::EditCommand | |
276 (&model, tr("Winnow Points")); | |
277 | 331 |
278 int counter = 0; | 332 int counter = 0; |
279 | 333 |
280 for (auto p: points) { | 334 for (auto p: allEvents) { |
281 | 335 |
282 if (ms) { | 336 if (ms) { |
283 Selection s(ms->getContainingSelection(p.frame, false)); | 337 Selection s(ms->getContainingSelection(p.getFrame(), false)); |
284 if (!s.contains(p.frame)) { | 338 if (!s.contains(p.getFrame())) { |
285 counter = 0; | 339 counter = 0; |
286 continue; | 340 continue; |
287 } | 341 } |
288 } | 342 } |
289 | 343 |
293 if (counter == 1) { | 347 if (counter == 1) { |
294 // this is an Nth instant, don't remove it | 348 // this is an Nth instant, don't remove it |
295 continue; | 349 continue; |
296 } | 350 } |
297 | 351 |
298 command->deletePoint(p); | 352 command->remove(p); |
299 } | 353 } |
300 | 354 |
301 return command->finish(); | 355 return command->finish(); |
302 } | |
303 | |
304 template <typename PointType> | |
305 void setValue(PointType &newPoint, PointType *prevPoint = 0) { | |
306 if (m_type == ValueFromExistingNeighbour) { | |
307 if (!prevPoint) { | |
308 std::cerr << "ERROR: Labeller::setValue: Previous point required but not provided" << std::endl; | |
309 } else { | |
310 newPoint.value = prevPoint->value; | |
311 } | |
312 } else { | |
313 float value = getValueFor<PointType>(newPoint, prevPoint); | |
314 if (actingOnPrevPoint() && prevPoint) { | |
315 prevPoint->value = value; | |
316 } else { | |
317 newPoint.value = value; | |
318 } | |
319 } | |
320 } | 356 } |
321 | 357 |
322 bool requiresPrevPoint() const { | 358 bool requiresPrevPoint() const { |
323 return (m_type == ValueFromDurationFromPrevious || | 359 return (m_type == ValueFromDurationFromPrevious || |
324 m_type == ValueFromDurationToNext || | 360 m_type == ValueFromDurationToNext || |
325 m_type == ValueFromTempoFromPrevious || | 361 m_type == ValueFromTempoFromPrevious || |
326 m_type == ValueFromDurationToNext); | 362 m_type == ValueFromDurationToNext); |
327 } | 363 } |
328 | 364 |
329 bool actingOnPrevPoint() const { | 365 bool actingOnPrevEvent() const { |
330 return (m_type == ValueFromDurationToNext || | 366 return (m_type == ValueFromDurationToNext || |
331 m_type == ValueFromTempoToNext); | 367 m_type == ValueFromTempoToNext); |
332 } | 368 } |
333 | 369 |
334 protected: | 370 protected: |
335 template <typename PointType> | 371 float getValueFor(Event p, const Event *prev) { |
336 float getValueFor(PointType &newPoint, PointType *prevPoint) | 372 |
337 { | |
338 float value = 0.f; | 373 float value = 0.f; |
339 | 374 |
340 switch (m_type) { | 375 switch (m_type) { |
341 | 376 |
342 case ValueNone: | 377 case ValueNone: |
353 value = float(m_counter2 + double(m_counter) / double(m_dp)); | 388 value = float(m_counter2 + double(m_counter) / double(m_dp)); |
354 incrementCounter(); | 389 incrementCounter(); |
355 break; | 390 break; |
356 | 391 |
357 case ValueFromFrameNumber: | 392 case ValueFromFrameNumber: |
358 value = float(newPoint.frame); | 393 value = float(p.getFrame()); |
359 break; | 394 break; |
360 | 395 |
361 case ValueFromRealTime: | 396 case ValueFromRealTime: |
362 if (m_rate == 0.0) { | 397 if (m_rate == 0.0) { |
363 std::cerr << "ERROR: Labeller::getValueFor: Real-time conversion required, but no sample rate set" << std::endl; | 398 std::cerr << "ERROR: Labeller::getValueFor: Real-time conversion required, but no sample rate set" << std::endl; |
364 } else { | 399 } else { |
365 value = float(double(newPoint.frame) / m_rate); | 400 value = float(double(p.getFrame()) / m_rate); |
366 } | 401 } |
367 break; | 402 break; |
368 | 403 |
369 case ValueFromDurationToNext: | 404 case ValueFromDurationToNext: |
370 case ValueFromTempoToNext: | 405 case ValueFromTempoToNext: |
371 case ValueFromDurationFromPrevious: | 406 case ValueFromDurationFromPrevious: |
372 case ValueFromTempoFromPrevious: | 407 case ValueFromTempoFromPrevious: |
373 if (m_rate == 0.0) { | 408 if (m_rate == 0.0) { |
374 std::cerr << "ERROR: Labeller::getValueFor: Real-time conversion required, but no sample rate set" << std::endl; | 409 std::cerr << "ERROR: Labeller::getValueFor: Real-time conversion required, but no sample rate set" << std::endl; |
375 } else if (!prevPoint) { | 410 } else if (!prev) { |
376 std::cerr << "ERROR: Labeller::getValueFor: Time difference required, but only one point provided" << std::endl; | 411 std::cerr << "ERROR: Labeller::getValueFor: Time difference required, but only one point provided" << std::endl; |
377 } else { | 412 } else { |
378 sv_frame_t f0 = prevPoint->frame, f1 = newPoint.frame; | 413 sv_frame_t f0 = prev->getFrame(), f1 = p.getFrame(); |
379 if (m_type == ValueFromDurationToNext || | 414 if (m_type == ValueFromDurationToNext || |
380 m_type == ValueFromDurationFromPrevious) { | 415 m_type == ValueFromDurationFromPrevious) { |
381 value = float(double(f1 - f0) / m_rate); | 416 value = float(double(f1 - f0) / m_rate); |
382 } else { | 417 } else { |
383 if (f1 > f0) { | 418 if (f1 > f0) { |
392 // function must handle points that don't have values to | 427 // function must handle points that don't have values to |
393 // read from | 428 // read from |
394 break; | 429 break; |
395 | 430 |
396 case ValueFromLabel: | 431 case ValueFromLabel: |
397 if (newPoint.label != "") { | 432 if (p.getLabel() != "") { |
398 // more forgiving than QString::toFloat() | 433 // more forgiving than QString::toFloat() |
399 value = float(atof(newPoint.label.toLocal8Bit())); | 434 value = float(atof(p.getLabel().toLocal8Bit())); |
400 } else { | 435 } else { |
401 value = 0.f; | 436 value = 0.f; |
402 } | 437 } |
403 break; | 438 break; |
404 } | 439 } |