annotate data/model/Labeller.h @ 1346:75ad55315db4 3.0-integration

More work on getting tests (especially file encoding ones) running on Windows. Various problems here to do with interaction with test filenames in Hg repos
author Chris Cannam
date Fri, 06 Jan 2017 15:44:55 +0000
parents 114de081a42d
children ad5f892c0c4d
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@305 16 #ifndef _LABELLER_H_
Chris@305 17 #define _LABELLER_H_
Chris@305 18
Chris@305 19 #include "SparseModel.h"
Chris@305 20 #include "SparseValueModel.h"
Chris@305 21
Chris@305 22 #include "base/Selection.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@305 65
Chris@355 66 Labeller(ValueType type = ValueNone) :
Chris@305 67 m_type(type),
Chris@305 68 m_counter(1),
Chris@305 69 m_counter2(1),
Chris@305 70 m_cycle(4),
Chris@305 71 m_dp(10),
Chris@305 72 m_rate(0) { }
Chris@305 73
Chris@305 74 Labeller(const Labeller &l) :
Chris@305 75 QObject(),
Chris@305 76 m_type(l.m_type),
Chris@380 77 m_counter(l.m_counter),
Chris@380 78 m_counter2(l.m_counter2),
Chris@305 79 m_cycle(l.m_cycle),
Chris@305 80 m_dp(l.m_dp),
Chris@305 81 m_rate(l.m_rate) { }
Chris@305 82
Chris@305 83 virtual ~Labeller() { }
Chris@305 84
Chris@305 85 typedef std::map<ValueType, QString> TypeNameMap;
Chris@305 86 TypeNameMap getTypeNames() const {
Chris@305 87 TypeNameMap m;
Chris@355 88 m[ValueNone]
Chris@355 89 = tr("No numbering");
Chris@355 90 m[ValueFromSimpleCounter]
Chris@355 91 = tr("Simple counter");
Chris@355 92 m[ValueFromCyclicalCounter]
Chris@355 93 = tr("Cyclical counter");
Chris@355 94 m[ValueFromTwoLevelCounter]
Chris@355 95 = tr("Cyclical two-level counter (bar/beat)");
Chris@355 96 m[ValueFromFrameNumber]
Chris@355 97 = tr("Audio sample frame number");
Chris@355 98 m[ValueFromRealTime]
Chris@355 99 = tr("Time in seconds");
Chris@355 100 m[ValueFromDurationToNext]
Chris@355 101 = tr("Duration to the following item");
Chris@355 102 m[ValueFromTempoToNext]
Chris@355 103 = tr("Tempo (bpm) based on duration to following item");
Chris@355 104 m[ValueFromDurationFromPrevious]
Chris@355 105 = tr("Duration since the previous item");
Chris@355 106 m[ValueFromTempoFromPrevious]
Chris@355 107 = tr("Tempo (bpm) based on duration since previous item");
Chris@355 108 m[ValueFromExistingNeighbour]
Chris@355 109 = tr("Same as the nearest previous item");
Chris@355 110 m[ValueFromLabel]
Chris@355 111 = tr("Value extracted from the item's label (where possible)");
Chris@305 112 return m;
Chris@305 113 }
Chris@305 114
Chris@305 115 ValueType getType() const { return m_type; }
Chris@305 116 void setType(ValueType type) { m_type = type; }
Chris@305 117
Chris@305 118 int getCounterValue() const { return m_counter; }
Chris@305 119 void setCounterValue(int v) { m_counter = v; }
Chris@305 120
Chris@305 121 int getSecondLevelCounterValue() const { return m_counter2; }
Chris@305 122 void setSecondLevelCounterValue(int v) { m_counter2 = v; }
Chris@305 123
Chris@305 124 int getCounterCycleSize() const { return m_cycle; }
Chris@305 125 void setCounterCycleSize(int s) {
Chris@305 126 m_cycle = s;
Chris@305 127 m_dp = 1;
Chris@305 128 while (s > 0) {
Chris@305 129 s /= 10;
Chris@305 130 m_dp *= 10;
Chris@305 131 }
Chris@307 132 if (m_counter > m_cycle) m_counter = 1;
Chris@305 133 }
Chris@305 134
Chris@1046 135 void setSampleRate(sv_samplerate_t rate) { m_rate = rate; }
Chris@305 136
Chris@830 137 void resetCounters() {
Chris@830 138 m_counter = 1;
Chris@830 139 m_counter2 = 1;
Chris@830 140 m_cycle = 4;
Chris@830 141 }
Chris@830 142
Chris@305 143 void incrementCounter() {
Chris@305 144 m_counter++;
Chris@305 145 if (m_type == ValueFromCyclicalCounter ||
Chris@305 146 m_type == ValueFromTwoLevelCounter) {
Chris@305 147 if (m_counter > m_cycle) {
Chris@305 148 m_counter = 1;
Chris@305 149 m_counter2++;
Chris@305 150 }
Chris@305 151 }
Chris@305 152 }
Chris@305 153
Chris@305 154 template <typename PointType>
Chris@305 155 void label(PointType &newPoint, PointType *prevPoint = 0) {
Chris@305 156 if (m_type == ValueNone) {
Chris@305 157 newPoint.label = "";
Chris@305 158 } else if (m_type == ValueFromTwoLevelCounter) {
Chris@305 159 newPoint.label = tr("%1.%2").arg(m_counter2).arg(m_counter);
Chris@305 160 incrementCounter();
Chris@306 161 } else if (m_type == ValueFromFrameNumber) {
Chris@306 162 // avoid going through floating-point value
Chris@306 163 newPoint.label = tr("%1").arg(newPoint.frame);
Chris@305 164 } else {
Chris@305 165 float value = getValueFor<PointType>(newPoint, prevPoint);
Chris@305 166 if (actingOnPrevPoint() && prevPoint) {
Chris@305 167 prevPoint->label = QString("%1").arg(value);
Chris@305 168 } else {
Chris@305 169 newPoint.label = QString("%1").arg(value);
Chris@305 170 }
Chris@305 171 }
Chris@305 172 }
Chris@305 173
Chris@1291 174 /**
Chris@1291 175 * Relabel all points in the given model that lie within the given
Chris@1291 176 * multi-selection, according to the labelling properties of this
Chris@1291 177 * labeller. Return a command that has been executed but not yet
Chris@1291 178 * added to the history.
Chris@1291 179 */
Chris@305 180 template <typename PointType>
Chris@1291 181 Command *labelAll(SparseModel<PointType> &model, MultiSelection *ms) {
Chris@305 182
Chris@1293 183 auto points(model.getPoints());
Chris@1293 184 auto command = new typename SparseModel<PointType>::EditCommand
Chris@305 185 (&model, tr("Label Points"));
Chris@305 186
Chris@305 187 PointType prevPoint(0);
Chris@1293 188 bool havePrevPoint(false);
Chris@305 189
Chris@1293 190 for (auto p: points) {
Chris@305 191
Chris@305 192 if (ms) {
Chris@1293 193 Selection s(ms->getContainingSelection(p.frame, false));
Chris@1293 194 if (!s.contains(p.frame)) {
Chris@1293 195 prevPoint = p;
Chris@1293 196 havePrevPoint = true;
Chris@1293 197 continue;
Chris@305 198 }
Chris@305 199 }
Chris@305 200
Chris@305 201 if (actingOnPrevPoint()) {
Chris@1293 202 if (havePrevPoint) {
Chris@305 203 command->deletePoint(prevPoint);
Chris@305 204 label<PointType>(p, &prevPoint);
Chris@305 205 command->addPoint(prevPoint);
Chris@305 206 }
Chris@305 207 } else {
Chris@305 208 command->deletePoint(p);
Chris@305 209 label<PointType>(p, &prevPoint);
Chris@305 210 command->addPoint(p);
Chris@305 211 }
Chris@305 212
Chris@305 213 prevPoint = p;
Chris@1293 214 havePrevPoint = true;
Chris@305 215 }
Chris@305 216
Chris@1291 217 return command->finish();
Chris@1291 218 }
Chris@1291 219
Chris@1291 220 /**
Chris@1291 221 * For each point in the given model (except the last), if that
Chris@1291 222 * point lies within the given multi-selection, add n-1 new points
Chris@1291 223 * at equally spaced intervals between it and the following point.
Chris@1291 224 * Return a command that has been executed but not yet added to
Chris@1291 225 * the history.
Chris@1291 226 */
Chris@1291 227 template <typename PointType>
Chris@1291 228 Command *subdivide(SparseModel<PointType> &model, MultiSelection *ms, int n) {
Chris@1291 229
Chris@1293 230 auto points(model.getPoints());
Chris@1293 231 auto command = new typename SparseModel<PointType>::EditCommand
Chris@1293 232 (&model, tr("Subdivide Points"));
Chris@1291 233
Chris@1293 234 for (auto i = points.begin(); i != points.end(); ++i) {
Chris@1291 235
Chris@1291 236 auto j = i;
Chris@1291 237 // require a "next point" even if it's not in selection
Chris@1293 238 if (++j == points.end()) {
Chris@1291 239 break;
Chris@1291 240 }
Chris@1291 241
Chris@1291 242 if (ms) {
Chris@1291 243 Selection s(ms->getContainingSelection(i->frame, false));
Chris@1293 244 if (!s.contains(i->frame)) {
Chris@1293 245 continue;
Chris@1291 246 }
Chris@1291 247 }
Chris@1291 248
Chris@1291 249 PointType p(*i);
Chris@1291 250 PointType nextP(*j);
Chris@1291 251
Chris@1291 252 // n is the number of subdivisions, so we add n-1 new
Chris@1291 253 // points equally spaced between p and nextP
Chris@1291 254
Chris@1291 255 for (int m = 1; m < n; ++m) {
Chris@1291 256 sv_frame_t f = p.frame + (m * (nextP.frame - p.frame)) / n;
Chris@1291 257 PointType newPoint(p);
Chris@1291 258 newPoint.frame = f;
Chris@1291 259 newPoint.label = tr("%1.%2").arg(p.label).arg(m+1);
Chris@1291 260 command->addPoint(newPoint);
Chris@1291 261 }
Chris@1291 262 }
Chris@1291 263
Chris@1291 264 return command->finish();
Chris@305 265 }
Chris@305 266
Chris@1292 267 /**
Chris@1292 268 * Return a command that has been executed but not yet added to
Chris@1292 269 * the history.
Chris@1292 270 */
Chris@1292 271 template <typename PointType>
Chris@1292 272 Command *winnow(SparseModel<PointType> &model, MultiSelection *ms, int n) {
Chris@1292 273
Chris@1293 274 auto points(model.getPoints());
Chris@1293 275 auto command = new typename SparseModel<PointType>::EditCommand
Chris@1293 276 (&model, tr("Winnow Points"));
Chris@1292 277
Chris@1292 278 int counter = 0;
Chris@1292 279
Chris@1293 280 for (auto p: points) {
Chris@1292 281
Chris@1292 282 if (ms) {
Chris@1293 283 Selection s(ms->getContainingSelection(p.frame, false));
Chris@1293 284 if (!s.contains(p.frame)) {
Chris@1293 285 counter = 0;
Chris@1293 286 continue;
Chris@1292 287 }
Chris@1292 288 }
Chris@1292 289
Chris@1292 290 ++counter;
Chris@1292 291
Chris@1292 292 if (counter == n+1) counter = 1;
Chris@1292 293 if (counter == 1) {
Chris@1292 294 // this is an Nth instant, don't remove it
Chris@1292 295 continue;
Chris@1292 296 }
Chris@1292 297
Chris@1293 298 command->deletePoint(p);
Chris@1292 299 }
Chris@1292 300
Chris@1292 301 return command->finish();
Chris@1292 302 }
Chris@1292 303
Chris@305 304 template <typename PointType>
Chris@305 305 void setValue(PointType &newPoint, PointType *prevPoint = 0) {
Chris@305 306 if (m_type == ValueFromExistingNeighbour) {
Chris@305 307 if (!prevPoint) {
Chris@305 308 std::cerr << "ERROR: Labeller::setValue: Previous point required but not provided" << std::endl;
Chris@305 309 } else {
Chris@305 310 newPoint.value = prevPoint->value;
Chris@305 311 }
Chris@305 312 } else {
Chris@305 313 float value = getValueFor<PointType>(newPoint, prevPoint);
Chris@305 314 if (actingOnPrevPoint() && prevPoint) {
Chris@305 315 prevPoint->value = value;
Chris@305 316 } else {
Chris@305 317 newPoint.value = value;
Chris@305 318 }
Chris@305 319 }
Chris@305 320 }
Chris@305 321
Chris@355 322 bool requiresPrevPoint() const {
Chris@355 323 return (m_type == ValueFromDurationFromPrevious ||
Chris@355 324 m_type == ValueFromDurationToNext ||
Chris@355 325 m_type == ValueFromTempoFromPrevious ||
Chris@355 326 m_type == ValueFromDurationToNext);
Chris@355 327 }
Chris@355 328
Chris@305 329 bool actingOnPrevPoint() const {
Chris@355 330 return (m_type == ValueFromDurationToNext ||
Chris@355 331 m_type == ValueFromTempoToNext);
Chris@305 332 }
Chris@305 333
Chris@305 334 protected:
Chris@305 335 template <typename PointType>
Chris@305 336 float getValueFor(PointType &newPoint, PointType *prevPoint)
Chris@305 337 {
Chris@305 338 float value = 0.f;
Chris@305 339
Chris@305 340 switch (m_type) {
Chris@305 341
Chris@305 342 case ValueNone:
Chris@305 343 value = 0;
Chris@305 344 break;
Chris@305 345
Chris@305 346 case ValueFromSimpleCounter:
Chris@305 347 case ValueFromCyclicalCounter:
Chris@1046 348 value = float(m_counter);
Chris@305 349 incrementCounter();
Chris@305 350 break;
Chris@305 351
Chris@305 352 case ValueFromTwoLevelCounter:
Chris@1046 353 value = float(m_counter2 + double(m_counter) / double(m_dp));
Chris@305 354 incrementCounter();
Chris@305 355 break;
Chris@305 356
Chris@305 357 case ValueFromFrameNumber:
Chris@1046 358 value = float(newPoint.frame);
Chris@305 359 break;
Chris@305 360
Chris@305 361 case ValueFromRealTime:
Chris@1046 362 if (m_rate == 0.0) {
Chris@305 363 std::cerr << "ERROR: Labeller::getValueFor: Real-time conversion required, but no sample rate set" << std::endl;
Chris@305 364 } else {
Chris@1046 365 value = float(double(newPoint.frame) / m_rate);
Chris@305 366 }
Chris@305 367 break;
Chris@305 368
Chris@355 369 case ValueFromDurationToNext:
Chris@355 370 case ValueFromTempoToNext:
Chris@355 371 case ValueFromDurationFromPrevious:
Chris@355 372 case ValueFromTempoFromPrevious:
Chris@1046 373 if (m_rate == 0.0) {
Chris@305 374 std::cerr << "ERROR: Labeller::getValueFor: Real-time conversion required, but no sample rate set" << std::endl;
Chris@305 375 } else if (!prevPoint) {
Chris@305 376 std::cerr << "ERROR: Labeller::getValueFor: Time difference required, but only one point provided" << std::endl;
Chris@305 377 } else {
Chris@1046 378 sv_frame_t f0 = prevPoint->frame, f1 = newPoint.frame;
Chris@355 379 if (m_type == ValueFromDurationToNext ||
Chris@355 380 m_type == ValueFromDurationFromPrevious) {
Chris@1046 381 value = float(double(f1 - f0) / m_rate);
Chris@305 382 } else {
Chris@305 383 if (f1 > f0) {
Chris@1046 384 value = float((60.0 * m_rate) / double(f1 - f0));
Chris@305 385 }
Chris@305 386 }
Chris@305 387 }
Chris@305 388 break;
Chris@305 389
Chris@305 390 case ValueFromExistingNeighbour:
Chris@305 391 // need to deal with this in the calling function, as this
Chris@305 392 // function must handle points that don't have values to
Chris@305 393 // read from
Chris@305 394 break;
Chris@305 395
Chris@305 396 case ValueFromLabel:
Chris@305 397 if (newPoint.label != "") {
Chris@305 398 // more forgiving than QString::toFloat()
Chris@1046 399 value = float(atof(newPoint.label.toLocal8Bit()));
Chris@305 400 } else {
Chris@305 401 value = 0.f;
Chris@305 402 }
Chris@305 403 break;
Chris@305 404 }
Chris@305 405
Chris@305 406 return value;
Chris@305 407 }
Chris@305 408
Chris@305 409 ValueType m_type;
Chris@305 410 int m_counter;
Chris@305 411 int m_counter2;
Chris@305 412 int m_cycle;
Chris@305 413 int m_dp;
Chris@1046 414 sv_samplerate_t m_rate;
Chris@305 415 };
Chris@305 416
Chris@305 417 #endif