annotate data/model/Labeller.h @ 1777:d484490cdf69

Split EditableDenseThreeDimensionalModel into explicitly compressed and uncompressed variants. Simplifies the uncompressed version, and we may want to consider whether we need the compressed one at all.
author Chris Cannam
date Tue, 10 Sep 2019 16:34:47 +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