annotate data/model/Labeller.h @ 1741:9d82b164f264 by-id

Work on commands, and some other model updates
author Chris Cannam
date Thu, 27 Jun 2019 13:08:10 +0100
parents 08bed13d3a26
children 52705a328b34
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@1741 228 * executed but not yet added to the history. The template
Chris@1741 229 * parameter must be a type that can be dynamic_cast to
Chris@1741 230 * EventEditable and that has a ById store.
Chris@1291 231 */
Chris@1741 232 template <typename EditableBase>
Chris@1741 233 Command *labelAll(typename EditableBase::Id editable,
Chris@1652 234 MultiSelection *ms,
Chris@1652 235 const EventVector &allEvents) {
Chris@305 236
Chris@1741 237 auto command = new ChangeEventsCommand<EditableBase>
Chris@1652 238 (editable, tr("Label Points"));
Chris@305 239
Chris@1652 240 Event prev;
Chris@1652 241 bool havePrev = false;
Chris@305 242
Chris@1652 243 for (auto p: allEvents) {
Chris@305 244
Chris@305 245 if (ms) {
Chris@1652 246 Selection s(ms->getContainingSelection(p.getFrame(), false));
Chris@1652 247 if (!s.contains(p.getFrame())) {
Chris@1652 248 prev = p;
Chris@1652 249 havePrev = true;
Chris@1293 250 continue;
Chris@305 251 }
Chris@305 252 }
Chris@305 253
Chris@1652 254 auto labelling = label(p, havePrev ? &prev : nullptr);
Chris@1652 255
Chris@1652 256 if (labelling.first == AppliesToThisEvent) {
Chris@1652 257 command->remove(p);
Chris@305 258 } else {
Chris@1652 259 command->remove(prev);
Chris@305 260 }
Chris@305 261
Chris@1652 262 command->add(labelling.second);
Chris@1652 263
Chris@1652 264 prev = p;
Chris@1652 265 havePrev = true;
Chris@305 266 }
Chris@305 267
Chris@1291 268 return command->finish();
Chris@1291 269 }
Chris@1291 270
Chris@1291 271 /**
Chris@1652 272 * For each event in the given event vector (except the last), if
Chris@1652 273 * that event lies within the given multi-selection, add n-1 new
Chris@1652 274 * events at equally spaced intervals between it and the following
Chris@1652 275 * event. Return a command that has been executed but not yet
Chris@1741 276 * added to the history. The template parameter must be a type
Chris@1741 277 * that can be dynamic_cast to EventEditable and that has a ById
Chris@1741 278 * store.
Chris@1291 279 */
Chris@1741 280 template <typename EditableBase>
Chris@1741 281 Command *subdivide(typename EditableBase::Id editable,
Chris@1652 282 MultiSelection *ms,
Chris@1652 283 const EventVector &allEvents,
Chris@1652 284 int n) {
Chris@1291 285
Chris@1741 286 auto command = new ChangeEventsCommand<EditableBase>
Chris@1652 287 (editable, tr("Subdivide Points"));
Chris@1652 288
Chris@1652 289 for (auto i = allEvents.begin(); i != allEvents.end(); ++i) {
Chris@1291 290
Chris@1291 291 auto j = i;
Chris@1291 292 // require a "next point" even if it's not in selection
Chris@1652 293 if (++j == allEvents.end()) {
Chris@1291 294 break;
Chris@1291 295 }
Chris@1291 296
Chris@1291 297 if (ms) {
Chris@1652 298 Selection s(ms->getContainingSelection(i->getFrame(), false));
Chris@1652 299 if (!s.contains(i->getFrame())) {
Chris@1293 300 continue;
Chris@1291 301 }
Chris@1291 302 }
Chris@1291 303
Chris@1652 304 Event p(*i);
Chris@1652 305 Event nextP(*j);
Chris@1291 306
Chris@1291 307 // n is the number of subdivisions, so we add n-1 new
Chris@1291 308 // points equally spaced between p and nextP
Chris@1291 309
Chris@1291 310 for (int m = 1; m < n; ++m) {
Chris@1652 311 sv_frame_t f = p.getFrame() +
Chris@1652 312 (m * (nextP.getFrame() - p.getFrame())) / n;
Chris@1652 313 Event newPoint = p
Chris@1652 314 .withFrame(f)
Chris@1652 315 .withLabel(tr("%1.%2").arg(p.getLabel()).arg(m+1));
Chris@1652 316 command->add(newPoint);
Chris@1291 317 }
Chris@1291 318 }
Chris@1291 319
Chris@1291 320 return command->finish();
Chris@305 321 }
Chris@305 322
Chris@1292 323 /**
Chris@1652 324 * The opposite of subdivide. Given an event vector, a
Chris@1652 325 * multi-selection, and a number n, remove all but every nth event
Chris@1652 326 * from the vector within the extents of the multi-selection.
Chris@1292 327 * Return a command that has been executed but not yet added to
Chris@1741 328 * the history. The template parameter must be a type
Chris@1741 329 * that can be dynamic_cast to EventEditable and that has a ById
Chris@1741 330 * store.
Chris@1292 331 */
Chris@1741 332 template <typename EditableBase>
Chris@1741 333 Command *winnow(typename EditableBase::Id editable,
Chris@1652 334 MultiSelection *ms,
Chris@1652 335 const EventVector &allEvents,
Chris@1652 336 int n) {
Chris@1652 337
Chris@1741 338 auto command = new ChangeEventsCommand<EditableBase>
Chris@1652 339 (editable, tr("Winnow Points"));
Chris@1292 340
Chris@1292 341 int counter = 0;
Chris@1292 342
Chris@1652 343 for (auto p: allEvents) {
Chris@1292 344
Chris@1292 345 if (ms) {
Chris@1652 346 Selection s(ms->getContainingSelection(p.getFrame(), false));
Chris@1652 347 if (!s.contains(p.getFrame())) {
Chris@1293 348 counter = 0;
Chris@1293 349 continue;
Chris@1292 350 }
Chris@1292 351 }
Chris@1292 352
Chris@1292 353 ++counter;
Chris@1292 354
Chris@1292 355 if (counter == n+1) counter = 1;
Chris@1292 356 if (counter == 1) {
Chris@1292 357 // this is an Nth instant, don't remove it
Chris@1292 358 continue;
Chris@1292 359 }
Chris@1292 360
Chris@1652 361 command->remove(p);
Chris@1292 362 }
Chris@1292 363
Chris@1292 364 return command->finish();
Chris@1292 365 }
Chris@1292 366
Chris@355 367 bool requiresPrevPoint() const {
Chris@355 368 return (m_type == ValueFromDurationFromPrevious ||
Chris@355 369 m_type == ValueFromDurationToNext ||
Chris@355 370 m_type == ValueFromTempoFromPrevious ||
Chris@355 371 m_type == ValueFromDurationToNext);
Chris@355 372 }
Chris@355 373
Chris@1652 374 bool actingOnPrevEvent() const {
Chris@355 375 return (m_type == ValueFromDurationToNext ||
Chris@355 376 m_type == ValueFromTempoToNext);
Chris@305 377 }
Chris@305 378
Chris@305 379 protected:
Chris@1652 380 float getValueFor(Event p, const Event *prev) {
Chris@1652 381
Chris@305 382 float value = 0.f;
Chris@305 383
Chris@305 384 switch (m_type) {
Chris@305 385
Chris@305 386 case ValueNone:
Chris@305 387 value = 0;
Chris@305 388 break;
Chris@305 389
Chris@305 390 case ValueFromSimpleCounter:
Chris@305 391 case ValueFromCyclicalCounter:
Chris@1046 392 value = float(m_counter);
Chris@305 393 incrementCounter();
Chris@305 394 break;
Chris@305 395
Chris@305 396 case ValueFromTwoLevelCounter:
Chris@1046 397 value = float(m_counter2 + double(m_counter) / double(m_dp));
Chris@305 398 incrementCounter();
Chris@305 399 break;
Chris@305 400
Chris@305 401 case ValueFromFrameNumber:
Chris@1652 402 value = float(p.getFrame());
Chris@305 403 break;
Chris@305 404
Chris@305 405 case ValueFromRealTime:
Chris@1046 406 if (m_rate == 0.0) {
Chris@305 407 std::cerr << "ERROR: Labeller::getValueFor: Real-time conversion required, but no sample rate set" << std::endl;
Chris@305 408 } else {
Chris@1652 409 value = float(double(p.getFrame()) / m_rate);
Chris@305 410 }
Chris@305 411 break;
Chris@305 412
Chris@355 413 case ValueFromDurationToNext:
Chris@355 414 case ValueFromTempoToNext:
Chris@355 415 case ValueFromDurationFromPrevious:
Chris@355 416 case ValueFromTempoFromPrevious:
Chris@1046 417 if (m_rate == 0.0) {
Chris@305 418 std::cerr << "ERROR: Labeller::getValueFor: Real-time conversion required, but no sample rate set" << std::endl;
Chris@1652 419 } else if (!prev) {
Chris@305 420 std::cerr << "ERROR: Labeller::getValueFor: Time difference required, but only one point provided" << std::endl;
Chris@305 421 } else {
Chris@1652 422 sv_frame_t f0 = prev->getFrame(), f1 = p.getFrame();
Chris@355 423 if (m_type == ValueFromDurationToNext ||
Chris@355 424 m_type == ValueFromDurationFromPrevious) {
Chris@1046 425 value = float(double(f1 - f0) / m_rate);
Chris@305 426 } else {
Chris@305 427 if (f1 > f0) {
Chris@1046 428 value = float((60.0 * m_rate) / double(f1 - f0));
Chris@305 429 }
Chris@305 430 }
Chris@305 431 }
Chris@305 432 break;
Chris@305 433
Chris@305 434 case ValueFromExistingNeighbour:
Chris@305 435 // need to deal with this in the calling function, as this
Chris@305 436 // function must handle points that don't have values to
Chris@305 437 // read from
Chris@305 438 break;
Chris@305 439
Chris@305 440 case ValueFromLabel:
Chris@1652 441 if (p.getLabel() != "") {
Chris@305 442 // more forgiving than QString::toFloat()
Chris@1652 443 value = float(atof(p.getLabel().toLocal8Bit()));
Chris@305 444 } else {
Chris@305 445 value = 0.f;
Chris@305 446 }
Chris@305 447 break;
Chris@305 448 }
Chris@305 449
Chris@305 450 return value;
Chris@305 451 }
Chris@305 452
Chris@305 453 ValueType m_type;
Chris@305 454 int m_counter;
Chris@305 455 int m_counter2;
Chris@305 456 int m_cycle;
Chris@305 457 int m_dp;
Chris@1046 458 sv_samplerate_t m_rate;
Chris@305 459 };
Chris@305 460
Chris@305 461 #endif