annotate data/model/Labeller.h @ 1714:83cb6e9d769b

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