annotate data/model/Labeller.h @ 1292:4b0c968a581a subdivide-instants

Add Winnow as well
author Chris Cannam
date Fri, 25 Nov 2016 09:57:36 +0000
parents 22f66068b464
children 114de081a42d
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@305 183 typename SparseModel<PointType>::PointList::iterator i;
Chris@305 184 typename SparseModel<PointType>::PointList pl(model.getPoints());
Chris@305 185
Chris@305 186 typename SparseModel<PointType>::EditCommand *command =
Chris@305 187 new typename SparseModel<PointType>::EditCommand
Chris@305 188 (&model, tr("Label Points"));
Chris@305 189
Chris@305 190 PointType prevPoint(0);
Chris@305 191
Chris@305 192 for (i = pl.begin(); i != pl.end(); ++i) {
Chris@305 193
Chris@305 194 bool inRange = true;
Chris@305 195 if (ms) {
Chris@305 196 Selection s(ms->getContainingSelection(i->frame, false));
Chris@305 197 if (s.isEmpty() || !s.contains(i->frame)) {
Chris@305 198 inRange = false;
Chris@305 199 }
Chris@305 200 }
Chris@305 201
Chris@305 202 PointType p(*i);
Chris@305 203
Chris@305 204 if (!inRange) {
Chris@305 205 prevPoint = p;
Chris@305 206 continue;
Chris@305 207 }
Chris@305 208
Chris@305 209 if (actingOnPrevPoint()) {
Chris@305 210 if (i != pl.begin()) {
Chris@305 211 command->deletePoint(prevPoint);
Chris@305 212 label<PointType>(p, &prevPoint);
Chris@305 213 command->addPoint(prevPoint);
Chris@305 214 }
Chris@305 215 } else {
Chris@305 216 command->deletePoint(p);
Chris@305 217 label<PointType>(p, &prevPoint);
Chris@305 218 command->addPoint(p);
Chris@305 219 }
Chris@305 220
Chris@305 221 prevPoint = p;
Chris@305 222 }
Chris@305 223
Chris@1291 224 return command->finish();
Chris@1291 225 }
Chris@1291 226
Chris@1291 227 /**
Chris@1291 228 * For each point in the given model (except the last), if that
Chris@1291 229 * point lies within the given multi-selection, add n-1 new points
Chris@1291 230 * at equally spaced intervals between it and the following point.
Chris@1291 231 * Return a command that has been executed but not yet added to
Chris@1291 232 * the history.
Chris@1291 233 */
Chris@1291 234 template <typename PointType>
Chris@1291 235 Command *subdivide(SparseModel<PointType> &model, MultiSelection *ms, int n) {
Chris@1291 236
Chris@1291 237 typename SparseModel<PointType>::PointList::iterator i;
Chris@1291 238 typename SparseModel<PointType>::PointList pl(model.getPoints());
Chris@1291 239
Chris@1291 240 typename SparseModel<PointType>::EditCommand *command =
Chris@1291 241 new typename SparseModel<PointType>::EditCommand
Chris@1291 242 (&model, tr("Subdivide"));
Chris@1291 243
Chris@1291 244 for (i = pl.begin(); i != pl.end(); ++i) {
Chris@1291 245
Chris@1291 246 auto j = i;
Chris@1291 247 // require a "next point" even if it's not in selection
Chris@1291 248 if (++j == pl.end()) {
Chris@1291 249 break;
Chris@1291 250 }
Chris@1291 251
Chris@1291 252 bool inRange = true;
Chris@1291 253 if (ms) {
Chris@1291 254 Selection s(ms->getContainingSelection(i->frame, false));
Chris@1291 255 if (s.isEmpty() || !s.contains(i->frame)) {
Chris@1291 256 inRange = false;
Chris@1291 257 }
Chris@1291 258 }
Chris@1291 259 if (!inRange) {
Chris@1291 260 continue;
Chris@1291 261 }
Chris@1291 262
Chris@1291 263 PointType p(*i);
Chris@1291 264 PointType nextP(*j);
Chris@1291 265
Chris@1291 266 // n is the number of subdivisions, so we add n-1 new
Chris@1291 267 // points equally spaced between p and nextP
Chris@1291 268
Chris@1291 269 for (int m = 1; m < n; ++m) {
Chris@1291 270 sv_frame_t f = p.frame + (m * (nextP.frame - p.frame)) / n;
Chris@1291 271 PointType newPoint(p);
Chris@1291 272 newPoint.frame = f;
Chris@1291 273 newPoint.label = tr("%1.%2").arg(p.label).arg(m+1);
Chris@1291 274 command->addPoint(newPoint);
Chris@1291 275 }
Chris@1291 276 }
Chris@1291 277
Chris@1291 278 return command->finish();
Chris@305 279 }
Chris@305 280
Chris@1292 281 /**
Chris@1292 282 * Return a command that has been executed but not yet added to
Chris@1292 283 * the history.
Chris@1292 284 */
Chris@1292 285 template <typename PointType>
Chris@1292 286 Command *winnow(SparseModel<PointType> &model, MultiSelection *ms, int n) {
Chris@1292 287
Chris@1292 288 typename SparseModel<PointType>::PointList::iterator i;
Chris@1292 289 typename SparseModel<PointType>::PointList pl(model.getPoints());
Chris@1292 290
Chris@1292 291 typename SparseModel<PointType>::EditCommand *command =
Chris@1292 292 new typename SparseModel<PointType>::EditCommand
Chris@1292 293 (&model, tr("Subdivide"));
Chris@1292 294
Chris@1292 295 int counter = 0;
Chris@1292 296
Chris@1292 297 for (i = pl.begin(); i != pl.end(); ++i) {
Chris@1292 298
Chris@1292 299 bool inRange = true;
Chris@1292 300 if (ms) {
Chris@1292 301 Selection s(ms->getContainingSelection(i->frame, false));
Chris@1292 302 if (s.isEmpty() || !s.contains(i->frame)) {
Chris@1292 303 inRange = false;
Chris@1292 304 }
Chris@1292 305 }
Chris@1292 306 if (!inRange) {
Chris@1292 307 counter = 0;
Chris@1292 308 continue;
Chris@1292 309 }
Chris@1292 310
Chris@1292 311 ++counter;
Chris@1292 312
Chris@1292 313 if (counter == n+1) counter = 1;
Chris@1292 314 if (counter == 1) {
Chris@1292 315 // this is an Nth instant, don't remove it
Chris@1292 316 continue;
Chris@1292 317 }
Chris@1292 318
Chris@1292 319 command->deletePoint(*i);
Chris@1292 320 }
Chris@1292 321
Chris@1292 322 return command->finish();
Chris@1292 323 }
Chris@1292 324
Chris@305 325 template <typename PointType>
Chris@305 326 void setValue(PointType &newPoint, PointType *prevPoint = 0) {
Chris@305 327 if (m_type == ValueFromExistingNeighbour) {
Chris@305 328 if (!prevPoint) {
Chris@305 329 std::cerr << "ERROR: Labeller::setValue: Previous point required but not provided" << std::endl;
Chris@305 330 } else {
Chris@305 331 newPoint.value = prevPoint->value;
Chris@305 332 }
Chris@305 333 } else {
Chris@305 334 float value = getValueFor<PointType>(newPoint, prevPoint);
Chris@305 335 if (actingOnPrevPoint() && prevPoint) {
Chris@305 336 prevPoint->value = value;
Chris@305 337 } else {
Chris@305 338 newPoint.value = value;
Chris@305 339 }
Chris@305 340 }
Chris@305 341 }
Chris@305 342
Chris@355 343 bool requiresPrevPoint() const {
Chris@355 344 return (m_type == ValueFromDurationFromPrevious ||
Chris@355 345 m_type == ValueFromDurationToNext ||
Chris@355 346 m_type == ValueFromTempoFromPrevious ||
Chris@355 347 m_type == ValueFromDurationToNext);
Chris@355 348 }
Chris@355 349
Chris@305 350 bool actingOnPrevPoint() const {
Chris@355 351 return (m_type == ValueFromDurationToNext ||
Chris@355 352 m_type == ValueFromTempoToNext);
Chris@305 353 }
Chris@305 354
Chris@305 355 protected:
Chris@305 356 template <typename PointType>
Chris@305 357 float getValueFor(PointType &newPoint, PointType *prevPoint)
Chris@305 358 {
Chris@305 359 float value = 0.f;
Chris@305 360
Chris@305 361 switch (m_type) {
Chris@305 362
Chris@305 363 case ValueNone:
Chris@305 364 value = 0;
Chris@305 365 break;
Chris@305 366
Chris@305 367 case ValueFromSimpleCounter:
Chris@305 368 case ValueFromCyclicalCounter:
Chris@1046 369 value = float(m_counter);
Chris@305 370 incrementCounter();
Chris@305 371 break;
Chris@305 372
Chris@305 373 case ValueFromTwoLevelCounter:
Chris@1046 374 value = float(m_counter2 + double(m_counter) / double(m_dp));
Chris@305 375 incrementCounter();
Chris@305 376 break;
Chris@305 377
Chris@305 378 case ValueFromFrameNumber:
Chris@1046 379 value = float(newPoint.frame);
Chris@305 380 break;
Chris@305 381
Chris@305 382 case ValueFromRealTime:
Chris@1046 383 if (m_rate == 0.0) {
Chris@305 384 std::cerr << "ERROR: Labeller::getValueFor: Real-time conversion required, but no sample rate set" << std::endl;
Chris@305 385 } else {
Chris@1046 386 value = float(double(newPoint.frame) / m_rate);
Chris@305 387 }
Chris@305 388 break;
Chris@305 389
Chris@355 390 case ValueFromDurationToNext:
Chris@355 391 case ValueFromTempoToNext:
Chris@355 392 case ValueFromDurationFromPrevious:
Chris@355 393 case ValueFromTempoFromPrevious:
Chris@1046 394 if (m_rate == 0.0) {
Chris@305 395 std::cerr << "ERROR: Labeller::getValueFor: Real-time conversion required, but no sample rate set" << std::endl;
Chris@305 396 } else if (!prevPoint) {
Chris@305 397 std::cerr << "ERROR: Labeller::getValueFor: Time difference required, but only one point provided" << std::endl;
Chris@305 398 } else {
Chris@1046 399 sv_frame_t f0 = prevPoint->frame, f1 = newPoint.frame;
Chris@355 400 if (m_type == ValueFromDurationToNext ||
Chris@355 401 m_type == ValueFromDurationFromPrevious) {
Chris@1046 402 value = float(double(f1 - f0) / m_rate);
Chris@305 403 } else {
Chris@305 404 if (f1 > f0) {
Chris@1046 405 value = float((60.0 * m_rate) / double(f1 - f0));
Chris@305 406 }
Chris@305 407 }
Chris@305 408 }
Chris@305 409 break;
Chris@305 410
Chris@305 411 case ValueFromExistingNeighbour:
Chris@305 412 // need to deal with this in the calling function, as this
Chris@305 413 // function must handle points that don't have values to
Chris@305 414 // read from
Chris@305 415 break;
Chris@305 416
Chris@305 417 case ValueFromLabel:
Chris@305 418 if (newPoint.label != "") {
Chris@305 419 // more forgiving than QString::toFloat()
Chris@1046 420 value = float(atof(newPoint.label.toLocal8Bit()));
Chris@305 421 } else {
Chris@305 422 value = 0.f;
Chris@305 423 }
Chris@305 424 break;
Chris@305 425 }
Chris@305 426
Chris@305 427 return value;
Chris@305 428 }
Chris@305 429
Chris@305 430 ValueType m_type;
Chris@305 431 int m_counter;
Chris@305 432 int m_counter2;
Chris@305 433 int m_cycle;
Chris@305 434 int m_dp;
Chris@1046 435 sv_samplerate_t m_rate;
Chris@305 436 };
Chris@305 437
Chris@305 438 #endif