annotate data/model/Labeller.h @ 1798:13bd41bd8a17

Some work on making Model classes thread-safe in typical use - and documenting this. Some of the implementations are simpler now that EventSeries is thread-safe
author Chris Cannam
date Tue, 01 Oct 2019 11:22:48 +0100
parents 52705a328b34
children
rev   line source
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@1581 16 #ifndef SV_LABELLER_H
Chris@1581 17 #define SV_LABELLER_H
Chris@305 18
Chris@1652 19 #include "base/Selection.h"
Chris@1652 20 #include "base/Event.h"
Chris@305 21
Chris@1652 22 #include "EventCommands.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@1652 65 //
Chris@1652 66 // 5. generate new labelled points in the gaps between other
Chris@1652 67 // points (subdivide), or remove them (winnow)
Chris@305 68
Chris@355 69 Labeller(ValueType type = ValueNone) :
Chris@305 70 m_type(type),
Chris@305 71 m_counter(1),
Chris@305 72 m_counter2(1),
Chris@305 73 m_cycle(4),
Chris@305 74 m_dp(10),
Chris@305 75 m_rate(0) { }
Chris@305 76
Chris@305 77 Labeller(const Labeller &l) :
Chris@305 78 QObject(),
Chris@305 79 m_type(l.m_type),
Chris@380 80 m_counter(l.m_counter),
Chris@380 81 m_counter2(l.m_counter2),
Chris@305 82 m_cycle(l.m_cycle),
Chris@305 83 m_dp(l.m_dp),
Chris@305 84 m_rate(l.m_rate) { }
Chris@305 85
Chris@305 86 virtual ~Labeller() { }
Chris@305 87
Chris@305 88 typedef std::map<ValueType, QString> TypeNameMap;
Chris@305 89 TypeNameMap getTypeNames() const {
Chris@305 90 TypeNameMap m;
Chris@355 91 m[ValueNone]
Chris@355 92 = tr("No numbering");
Chris@355 93 m[ValueFromSimpleCounter]
Chris@355 94 = tr("Simple counter");
Chris@355 95 m[ValueFromCyclicalCounter]
Chris@355 96 = tr("Cyclical counter");
Chris@355 97 m[ValueFromTwoLevelCounter]
Chris@355 98 = tr("Cyclical two-level counter (bar/beat)");
Chris@355 99 m[ValueFromFrameNumber]
Chris@355 100 = tr("Audio sample frame number");
Chris@355 101 m[ValueFromRealTime]
Chris@355 102 = tr("Time in seconds");
Chris@355 103 m[ValueFromDurationToNext]
Chris@355 104 = tr("Duration to the following item");
Chris@355 105 m[ValueFromTempoToNext]
Chris@355 106 = tr("Tempo (bpm) based on duration to following item");
Chris@355 107 m[ValueFromDurationFromPrevious]
Chris@355 108 = tr("Duration since the previous item");
Chris@355 109 m[ValueFromTempoFromPrevious]
Chris@355 110 = tr("Tempo (bpm) based on duration since previous item");
Chris@355 111 m[ValueFromExistingNeighbour]
Chris@355 112 = tr("Same as the nearest previous item");
Chris@355 113 m[ValueFromLabel]
Chris@355 114 = tr("Value extracted from the item's label (where possible)");
Chris@305 115 return m;
Chris@305 116 }
Chris@305 117
Chris@305 118 ValueType getType() const { return m_type; }
Chris@305 119 void setType(ValueType type) { m_type = type; }
Chris@305 120
Chris@305 121 int getCounterValue() const { return m_counter; }
Chris@305 122 void setCounterValue(int v) { m_counter = v; }
Chris@305 123
Chris@305 124 int getSecondLevelCounterValue() const { return m_counter2; }
Chris@305 125 void setSecondLevelCounterValue(int v) { m_counter2 = v; }
Chris@305 126
Chris@305 127 int getCounterCycleSize() const { return m_cycle; }
Chris@305 128 void setCounterCycleSize(int s) {
Chris@305 129 m_cycle = s;
Chris@305 130 m_dp = 1;
Chris@305 131 while (s > 0) {
Chris@305 132 s /= 10;
Chris@305 133 m_dp *= 10;
Chris@305 134 }
Chris@307 135 if (m_counter > m_cycle) m_counter = 1;
Chris@305 136 }
Chris@305 137
Chris@1046 138 void setSampleRate(sv_samplerate_t rate) { m_rate = rate; }
Chris@305 139
Chris@830 140 void resetCounters() {
Chris@830 141 m_counter = 1;
Chris@830 142 m_counter2 = 1;
Chris@830 143 m_cycle = 4;
Chris@830 144 }
Chris@830 145
Chris@305 146 void incrementCounter() {
Chris@305 147 m_counter++;
Chris@305 148 if (m_type == ValueFromCyclicalCounter ||
Chris@305 149 m_type == ValueFromTwoLevelCounter) {
Chris@305 150 if (m_counter > m_cycle) {
Chris@305 151 m_counter = 1;
Chris@305 152 m_counter2++;
Chris@305 153 }
Chris@305 154 }
Chris@305 155 }
Chris@305 156
Chris@1652 157 enum Application {
Chris@1652 158 AppliesToThisEvent,
Chris@1652 159 AppliesToPreviousEvent
Chris@1652 160 };
Chris@1652 161 typedef std::pair<Application, Event> Relabelling;
Chris@1652 162 typedef std::pair<Application, Event> Revaluing;
Chris@1652 163
Chris@1652 164 /**
Chris@1652 165 * Return a labelled event based on the given event, previous
Chris@1652 166 * event if supplied, and internal labeller state. The returned
Chris@1652 167 * event replaces either the event provided or the previous event,
Chris@1652 168 * depending on the Application value in the returned pair.
Chris@1652 169 */
Chris@1652 170 Relabelling
Chris@1652 171 label(Event e, const Event *prev = nullptr) {
Chris@1652 172
Chris@1652 173 QString label = e.getLabel();
Chris@1652 174
Chris@305 175 if (m_type == ValueNone) {
Chris@1652 176 label = "";
Chris@305 177 } else if (m_type == ValueFromTwoLevelCounter) {
Chris@1652 178 label = tr("%1.%2").arg(m_counter2).arg(m_counter);
Chris@305 179 incrementCounter();
Chris@306 180 } else if (m_type == ValueFromFrameNumber) {
Chris@306 181 // avoid going through floating-point value
Chris@1652 182 label = tr("%1").arg(e.getFrame());
Chris@305 183 } else {
Chris@1652 184 float value = getValueFor(e, prev);
Chris@1652 185 label = QString("%1").arg(value);
Chris@1652 186 }
Chris@1652 187
Chris@1652 188 if (actingOnPrevEvent() && prev) {
Chris@1652 189 return { AppliesToPreviousEvent, prev->withLabel(label) };
Chris@1652 190 } else {
Chris@1652 191 return { AppliesToThisEvent, e.withLabel(label) };
Chris@305 192 }
Chris@305 193 }
Chris@1652 194
Chris@1652 195 /**
Chris@1652 196 * Return an event with a value following the labelling scheme,
Chris@1652 197 * based on the given event, previous event if supplied, and
Chris@1652 198 * internal labeller state. The returned event replaces either the
Chris@1652 199 * event provided or the previous event, depending on the
Chris@1652 200 * Application value in the returned pair.
Chris@1652 201 */
Chris@1652 202 Revaluing
Chris@1652 203 revalue(Event e, const Event *prev = nullptr) {
Chris@1652 204
Chris@1652 205 float value = e.getValue();
Chris@305 206
Chris@1652 207 if (m_type == ValueFromExistingNeighbour) {
Chris@1652 208 if (!prev) {
Chris@1652 209 std::cerr << "ERROR: Labeller::setValue: Previous point required but not provided" << std::endl;
Chris@1652 210 } else {
Chris@1652 211 return { AppliesToThisEvent, e.withValue(prev->getValue()) };
Chris@1652 212 }
Chris@1652 213 } else {
Chris@1652 214 value = getValueFor(e, prev);
Chris@1652 215 }
Chris@1652 216
Chris@1652 217 if (actingOnPrevEvent() && prev) {
Chris@1652 218 return { AppliesToPreviousEvent, prev->withValue(value) };
Chris@1652 219 } else {
Chris@1652 220 return { AppliesToThisEvent, e.withValue(value) };
Chris@1652 221 }
Chris@1652 222 }
Chris@1652 223
Chris@1291 224 /**
Chris@1652 225 * Relabel all events in the given event vector that lie within
Chris@1652 226 * the given multi-selection, according to the labelling
Chris@1652 227 * properties of this labeller. Return a command that has been
Chris@1742 228 * executed but not yet added to the history. The id must be that
Chris@1742 229 * of a type that can be retrieved from the AnyById store and
Chris@1742 230 * dynamic_cast to EventEditable.
Chris@1291 231 */
Chris@1742 232 Command *labelAll(int editableId,
Chris@1652 233 MultiSelection *ms,
Chris@1652 234 const EventVector &allEvents) {
Chris@305 235
Chris@1742 236 auto command = new ChangeEventsCommand
Chris@1742 237 (editableId, tr("Label Points"));
Chris@305 238
Chris@1652 239 Event prev;
Chris@1652 240 bool havePrev = false;
Chris@305 241
Chris@1652 242 for (auto p: allEvents) {
Chris@305 243
Chris@305 244 if (ms) {
Chris@1652 245 Selection s(ms->getContainingSelection(p.getFrame(), false));
Chris@1652 246 if (!s.contains(p.getFrame())) {
Chris@1652 247 prev = p;
Chris@1652 248 havePrev = true;
Chris@1293 249 continue;
Chris@305 250 }
Chris@305 251 }
Chris@305 252
Chris@1652 253 auto labelling = label(p, havePrev ? &prev : nullptr);
Chris@1652 254
Chris@1652 255 if (labelling.first == AppliesToThisEvent) {
Chris@1652 256 command->remove(p);
Chris@305 257 } else {
Chris@1652 258 command->remove(prev);
Chris@305 259 }
Chris@305 260
Chris@1652 261 command->add(labelling.second);
Chris@1652 262
Chris@1652 263 prev = p;
Chris@1652 264 havePrev = true;
Chris@305 265 }
Chris@305 266
Chris@1291 267 return command->finish();
Chris@1291 268 }
Chris@1291 269
Chris@1291 270 /**
Chris@1652 271 * For each event in the given event vector (except the last), if
Chris@1652 272 * that event lies within the given multi-selection, add n-1 new
Chris@1652 273 * events at equally spaced intervals between it and the following
Chris@1652 274 * event. Return a command that has been executed but not yet
Chris@1742 275 * added to the history. The id must be that of a type that can
Chris@1742 276 * be retrieved from the AnyById store and dynamic_cast to
Chris@1742 277 * EventEditable.
Chris@1291 278 */
Chris@1742 279 Command *subdivide(int editableId,
Chris@1652 280 MultiSelection *ms,
Chris@1652 281 const EventVector &allEvents,
Chris@1652 282 int n) {
Chris@1291 283
Chris@1742 284 auto command = new ChangeEventsCommand
Chris@1742 285 (editableId, tr("Subdivide Points"));
Chris@1652 286
Chris@1652 287 for (auto i = allEvents.begin(); i != allEvents.end(); ++i) {
Chris@1291 288
Chris@1291 289 auto j = i;
Chris@1291 290 // require a "next point" even if it's not in selection
Chris@1652 291 if (++j == allEvents.end()) {
Chris@1291 292 break;
Chris@1291 293 }
Chris@1291 294
Chris@1291 295 if (ms) {
Chris@1652 296 Selection s(ms->getContainingSelection(i->getFrame(), false));
Chris@1652 297 if (!s.contains(i->getFrame())) {
Chris@1293 298 continue;
Chris@1291 299 }
Chris@1291 300 }
Chris@1291 301
Chris@1652 302 Event p(*i);
Chris@1652 303 Event nextP(*j);
Chris@1291 304
Chris@1291 305 // n is the number of subdivisions, so we add n-1 new
Chris@1291 306 // points equally spaced between p and nextP
Chris@1291 307
Chris@1291 308 for (int m = 1; m < n; ++m) {
Chris@1652 309 sv_frame_t f = p.getFrame() +
Chris@1652 310 (m * (nextP.getFrame() - p.getFrame())) / n;
Chris@1652 311 Event newPoint = p
Chris@1652 312 .withFrame(f)
Chris@1652 313 .withLabel(tr("%1.%2").arg(p.getLabel()).arg(m+1));
Chris@1652 314 command->add(newPoint);
Chris@1291 315 }
Chris@1291 316 }
Chris@1291 317
Chris@1291 318 return command->finish();
Chris@305 319 }
Chris@305 320
Chris@1292 321 /**
Chris@1652 322 * The opposite of subdivide. Given an event vector, a
Chris@1652 323 * multi-selection, and a number n, remove all but every nth event
Chris@1652 324 * from the vector within the extents of the multi-selection.
Chris@1292 325 * Return a command that has been executed but not yet added to
Chris@1742 326 * the history. The id must be that of a type that can be
Chris@1742 327 * retrieved from the AnyById store and dynamic_cast to
Chris@1742 328 * EventEditable.
Chris@1292 329 */
Chris@1742 330 Command *winnow(int editableId,
Chris@1652 331 MultiSelection *ms,
Chris@1652 332 const EventVector &allEvents,
Chris@1652 333 int n) {
Chris@1652 334
Chris@1742 335 auto command = new ChangeEventsCommand
Chris@1742 336 (editableId, tr("Winnow Points"));
Chris@1292 337
Chris@1292 338 int counter = 0;
Chris@1292 339
Chris@1652 340 for (auto p: allEvents) {
Chris@1292 341
Chris@1292 342 if (ms) {
Chris@1652 343 Selection s(ms->getContainingSelection(p.getFrame(), false));
Chris@1652 344 if (!s.contains(p.getFrame())) {
Chris@1293 345 counter = 0;
Chris@1293 346 continue;
Chris@1292 347 }
Chris@1292 348 }
Chris@1292 349
Chris@1292 350 ++counter;
Chris@1292 351
Chris@1292 352 if (counter == n+1) counter = 1;
Chris@1292 353 if (counter == 1) {
Chris@1292 354 // this is an Nth instant, don't remove it
Chris@1292 355 continue;
Chris@1292 356 }
Chris@1292 357
Chris@1652 358 command->remove(p);
Chris@1292 359 }
Chris@1292 360
Chris@1292 361 return command->finish();
Chris@1292 362 }
Chris@1292 363
Chris@355 364 bool requiresPrevPoint() const {
Chris@355 365 return (m_type == ValueFromDurationFromPrevious ||
Chris@355 366 m_type == ValueFromDurationToNext ||
Chris@355 367 m_type == ValueFromTempoFromPrevious ||
Chris@355 368 m_type == ValueFromDurationToNext);
Chris@355 369 }
Chris@355 370
Chris@1652 371 bool actingOnPrevEvent() const {
Chris@355 372 return (m_type == ValueFromDurationToNext ||
Chris@355 373 m_type == ValueFromTempoToNext);
Chris@305 374 }
Chris@305 375
Chris@305 376 protected:
Chris@1652 377 float getValueFor(Event p, const Event *prev) {
Chris@1652 378
Chris@305 379 float value = 0.f;
Chris@305 380
Chris@305 381 switch (m_type) {
Chris@305 382
Chris@305 383 case ValueNone:
Chris@305 384 value = 0;
Chris@305 385 break;
Chris@305 386
Chris@305 387 case ValueFromSimpleCounter:
Chris@305 388 case ValueFromCyclicalCounter:
Chris@1046 389 value = float(m_counter);
Chris@305 390 incrementCounter();
Chris@305 391 break;
Chris@305 392
Chris@305 393 case ValueFromTwoLevelCounter:
Chris@1046 394 value = float(m_counter2 + double(m_counter) / double(m_dp));
Chris@305 395 incrementCounter();
Chris@305 396 break;
Chris@305 397
Chris@305 398 case ValueFromFrameNumber:
Chris@1652 399 value = float(p.getFrame());
Chris@305 400 break;
Chris@305 401
Chris@305 402 case ValueFromRealTime:
Chris@1046 403 if (m_rate == 0.0) {
Chris@305 404 std::cerr << "ERROR: Labeller::getValueFor: Real-time conversion required, but no sample rate set" << std::endl;
Chris@305 405 } else {
Chris@1652 406 value = float(double(p.getFrame()) / m_rate);
Chris@305 407 }
Chris@305 408 break;
Chris@305 409
Chris@355 410 case ValueFromDurationToNext:
Chris@355 411 case ValueFromTempoToNext:
Chris@355 412 case ValueFromDurationFromPrevious:
Chris@355 413 case ValueFromTempoFromPrevious:
Chris@1046 414 if (m_rate == 0.0) {
Chris@305 415 std::cerr << "ERROR: Labeller::getValueFor: Real-time conversion required, but no sample rate set" << std::endl;
Chris@1652 416 } else if (!prev) {
Chris@305 417 std::cerr << "ERROR: Labeller::getValueFor: Time difference required, but only one point provided" << std::endl;
Chris@305 418 } else {
Chris@1652 419 sv_frame_t f0 = prev->getFrame(), f1 = p.getFrame();
Chris@355 420 if (m_type == ValueFromDurationToNext ||
Chris@355 421 m_type == ValueFromDurationFromPrevious) {
Chris@1046 422 value = float(double(f1 - f0) / m_rate);
Chris@305 423 } else {
Chris@305 424 if (f1 > f0) {
Chris@1046 425 value = float((60.0 * m_rate) / double(f1 - f0));
Chris@305 426 }
Chris@305 427 }
Chris@305 428 }
Chris@305 429 break;
Chris@305 430
Chris@305 431 case ValueFromExistingNeighbour:
Chris@305 432 // need to deal with this in the calling function, as this
Chris@305 433 // function must handle points that don't have values to
Chris@305 434 // read from
Chris@305 435 break;
Chris@305 436
Chris@305 437 case ValueFromLabel:
Chris@1652 438 if (p.getLabel() != "") {
Chris@305 439 // more forgiving than QString::toFloat()
Chris@1652 440 value = float(atof(p.getLabel().toLocal8Bit()));
Chris@305 441 } else {
Chris@305 442 value = 0.f;
Chris@305 443 }
Chris@305 444 break;
Chris@305 445 }
Chris@305 446
Chris@305 447 return value;
Chris@305 448 }
Chris@305 449
Chris@305 450 ValueType m_type;
Chris@305 451 int m_counter;
Chris@305 452 int m_counter2;
Chris@305 453 int m_cycle;
Chris@305 454 int m_dp;
Chris@1046 455 sv_samplerate_t m_rate;
Chris@305 456 };
Chris@305 457
Chris@305 458 #endif