Chris@305: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@305: Chris@305: /* Chris@305: Sonic Visualiser Chris@305: An audio file viewer and annotation editor. Chris@305: Centre for Digital Music, Queen Mary, University of London. Chris@305: This file copyright 2006-2007 Chris Cannam and QMUL. Chris@305: Chris@305: This program is free software; you can redistribute it and/or Chris@305: modify it under the terms of the GNU General Public License as Chris@305: published by the Free Software Foundation; either version 2 of the Chris@305: License, or (at your option) any later version. See the file Chris@305: COPYING included with this distribution for more information. Chris@305: */ Chris@305: Chris@1581: #ifndef SV_LABELLER_H Chris@1581: #define SV_LABELLER_H Chris@305: Chris@305: #include "SparseModel.h" Chris@305: #include "SparseValueModel.h" Chris@305: Chris@305: #include "base/Selection.h" Chris@305: Chris@305: #include Chris@305: Chris@305: #include Chris@305: #include Chris@305: Chris@305: class Labeller : public QObject Chris@305: { Chris@423: Q_OBJECT Chris@423: Chris@305: public: Chris@305: enum ValueType { Chris@305: ValueNone, Chris@305: ValueFromSimpleCounter, Chris@305: ValueFromCyclicalCounter, Chris@305: ValueFromTwoLevelCounter, Chris@305: ValueFromFrameNumber, Chris@305: ValueFromRealTime, Chris@355: ValueFromDurationFromPrevious, Chris@355: ValueFromDurationToNext, Chris@355: ValueFromTempoFromPrevious, Chris@355: ValueFromTempoToNext, Chris@305: ValueFromExistingNeighbour, Chris@305: ValueFromLabel Chris@305: }; Chris@305: Chris@305: // uses: Chris@305: // Chris@305: // 1. when adding points to a time-value model, generate values Chris@305: // for those points based on their times or labels or a counter Chris@305: // Chris@305: // 2. when adding a single point to a time-instant model, generate Chris@305: // a label for it based on its time and that of the previous point Chris@305: // or a counter Chris@305: // Chris@305: // 3. when adding a single point to a time-instant model, generate Chris@305: // a label for the previous point based on its time and that of Chris@305: // the point just added (as tempo is based on time to the next Chris@305: // point, not the previous one) Chris@305: // Chris@305: // 4. re-label a set of points that have already been added to a Chris@305: // model Chris@305: Chris@355: Labeller(ValueType type = ValueNone) : Chris@305: m_type(type), Chris@305: m_counter(1), Chris@305: m_counter2(1), Chris@305: m_cycle(4), Chris@305: m_dp(10), Chris@305: m_rate(0) { } Chris@305: Chris@305: Labeller(const Labeller &l) : Chris@305: QObject(), Chris@305: m_type(l.m_type), Chris@380: m_counter(l.m_counter), Chris@380: m_counter2(l.m_counter2), Chris@305: m_cycle(l.m_cycle), Chris@305: m_dp(l.m_dp), Chris@305: m_rate(l.m_rate) { } Chris@305: Chris@305: virtual ~Labeller() { } Chris@305: Chris@305: typedef std::map TypeNameMap; Chris@305: TypeNameMap getTypeNames() const { Chris@305: TypeNameMap m; Chris@355: m[ValueNone] Chris@355: = tr("No numbering"); Chris@355: m[ValueFromSimpleCounter] Chris@355: = tr("Simple counter"); Chris@355: m[ValueFromCyclicalCounter] Chris@355: = tr("Cyclical counter"); Chris@355: m[ValueFromTwoLevelCounter] Chris@355: = tr("Cyclical two-level counter (bar/beat)"); Chris@355: m[ValueFromFrameNumber] Chris@355: = tr("Audio sample frame number"); Chris@355: m[ValueFromRealTime] Chris@355: = tr("Time in seconds"); Chris@355: m[ValueFromDurationToNext] Chris@355: = tr("Duration to the following item"); Chris@355: m[ValueFromTempoToNext] Chris@355: = tr("Tempo (bpm) based on duration to following item"); Chris@355: m[ValueFromDurationFromPrevious] Chris@355: = tr("Duration since the previous item"); Chris@355: m[ValueFromTempoFromPrevious] Chris@355: = tr("Tempo (bpm) based on duration since previous item"); Chris@355: m[ValueFromExistingNeighbour] Chris@355: = tr("Same as the nearest previous item"); Chris@355: m[ValueFromLabel] Chris@355: = tr("Value extracted from the item's label (where possible)"); Chris@305: return m; Chris@305: } Chris@305: Chris@305: ValueType getType() const { return m_type; } Chris@305: void setType(ValueType type) { m_type = type; } Chris@305: Chris@305: int getCounterValue() const { return m_counter; } Chris@305: void setCounterValue(int v) { m_counter = v; } Chris@305: Chris@305: int getSecondLevelCounterValue() const { return m_counter2; } Chris@305: void setSecondLevelCounterValue(int v) { m_counter2 = v; } Chris@305: Chris@305: int getCounterCycleSize() const { return m_cycle; } Chris@305: void setCounterCycleSize(int s) { Chris@305: m_cycle = s; Chris@305: m_dp = 1; Chris@305: while (s > 0) { Chris@305: s /= 10; Chris@305: m_dp *= 10; Chris@305: } Chris@307: if (m_counter > m_cycle) m_counter = 1; Chris@305: } Chris@305: Chris@1046: void setSampleRate(sv_samplerate_t rate) { m_rate = rate; } Chris@305: Chris@830: void resetCounters() { Chris@830: m_counter = 1; Chris@830: m_counter2 = 1; Chris@830: m_cycle = 4; Chris@830: } Chris@830: Chris@305: void incrementCounter() { Chris@305: m_counter++; Chris@305: if (m_type == ValueFromCyclicalCounter || Chris@305: m_type == ValueFromTwoLevelCounter) { Chris@305: if (m_counter > m_cycle) { Chris@305: m_counter = 1; Chris@305: m_counter2++; Chris@305: } Chris@305: } Chris@305: } Chris@305: Chris@305: template Chris@305: void label(PointType &newPoint, PointType *prevPoint = 0) { Chris@305: if (m_type == ValueNone) { Chris@305: newPoint.label = ""; Chris@305: } else if (m_type == ValueFromTwoLevelCounter) { Chris@305: newPoint.label = tr("%1.%2").arg(m_counter2).arg(m_counter); Chris@305: incrementCounter(); Chris@306: } else if (m_type == ValueFromFrameNumber) { Chris@306: // avoid going through floating-point value Chris@306: newPoint.label = tr("%1").arg(newPoint.frame); Chris@305: } else { Chris@305: float value = getValueFor(newPoint, prevPoint); Chris@305: if (actingOnPrevPoint() && prevPoint) { Chris@305: prevPoint->label = QString("%1").arg(value); Chris@305: } else { Chris@305: newPoint.label = QString("%1").arg(value); Chris@305: } Chris@305: } Chris@305: } Chris@305: Chris@1291: /** Chris@1291: * Relabel all points in the given model that lie within the given Chris@1291: * multi-selection, according to the labelling properties of this Chris@1291: * labeller. Return a command that has been executed but not yet Chris@1291: * added to the history. Chris@1291: */ Chris@305: template Chris@1291: Command *labelAll(SparseModel &model, MultiSelection *ms) { Chris@305: Chris@1293: auto points(model.getPoints()); Chris@1293: auto command = new typename SparseModel::EditCommand Chris@305: (&model, tr("Label Points")); Chris@305: Chris@305: PointType prevPoint(0); Chris@1293: bool havePrevPoint(false); Chris@305: Chris@1293: for (auto p: points) { Chris@305: Chris@305: if (ms) { Chris@1293: Selection s(ms->getContainingSelection(p.frame, false)); Chris@1293: if (!s.contains(p.frame)) { Chris@1293: prevPoint = p; Chris@1293: havePrevPoint = true; Chris@1293: continue; Chris@305: } Chris@305: } Chris@305: Chris@305: if (actingOnPrevPoint()) { Chris@1293: if (havePrevPoint) { Chris@305: command->deletePoint(prevPoint); Chris@305: label(p, &prevPoint); Chris@305: command->addPoint(prevPoint); Chris@305: } Chris@305: } else { Chris@305: command->deletePoint(p); Chris@305: label(p, &prevPoint); Chris@305: command->addPoint(p); Chris@305: } Chris@305: Chris@305: prevPoint = p; Chris@1293: havePrevPoint = true; Chris@305: } Chris@305: Chris@1291: return command->finish(); Chris@1291: } Chris@1291: Chris@1291: /** Chris@1291: * For each point in the given model (except the last), if that Chris@1291: * point lies within the given multi-selection, add n-1 new points Chris@1291: * at equally spaced intervals between it and the following point. Chris@1291: * Return a command that has been executed but not yet added to Chris@1291: * the history. Chris@1291: */ Chris@1291: template Chris@1291: Command *subdivide(SparseModel &model, MultiSelection *ms, int n) { Chris@1291: Chris@1293: auto points(model.getPoints()); Chris@1293: auto command = new typename SparseModel::EditCommand Chris@1293: (&model, tr("Subdivide Points")); Chris@1291: Chris@1293: for (auto i = points.begin(); i != points.end(); ++i) { Chris@1291: Chris@1291: auto j = i; Chris@1291: // require a "next point" even if it's not in selection Chris@1293: if (++j == points.end()) { Chris@1291: break; Chris@1291: } Chris@1291: Chris@1291: if (ms) { Chris@1291: Selection s(ms->getContainingSelection(i->frame, false)); Chris@1293: if (!s.contains(i->frame)) { Chris@1293: continue; Chris@1291: } Chris@1291: } Chris@1291: Chris@1291: PointType p(*i); Chris@1291: PointType nextP(*j); Chris@1291: Chris@1291: // n is the number of subdivisions, so we add n-1 new Chris@1291: // points equally spaced between p and nextP Chris@1291: Chris@1291: for (int m = 1; m < n; ++m) { Chris@1291: sv_frame_t f = p.frame + (m * (nextP.frame - p.frame)) / n; Chris@1291: PointType newPoint(p); Chris@1291: newPoint.frame = f; Chris@1291: newPoint.label = tr("%1.%2").arg(p.label).arg(m+1); Chris@1291: command->addPoint(newPoint); Chris@1291: } Chris@1291: } Chris@1291: Chris@1291: return command->finish(); Chris@305: } Chris@305: Chris@1292: /** Chris@1292: * Return a command that has been executed but not yet added to Chris@1292: * the history. Chris@1292: */ Chris@1292: template Chris@1292: Command *winnow(SparseModel &model, MultiSelection *ms, int n) { Chris@1292: Chris@1293: auto points(model.getPoints()); Chris@1293: auto command = new typename SparseModel::EditCommand Chris@1293: (&model, tr("Winnow Points")); Chris@1292: Chris@1292: int counter = 0; Chris@1292: Chris@1293: for (auto p: points) { Chris@1292: Chris@1292: if (ms) { Chris@1293: Selection s(ms->getContainingSelection(p.frame, false)); Chris@1293: if (!s.contains(p.frame)) { Chris@1293: counter = 0; Chris@1293: continue; Chris@1292: } Chris@1292: } Chris@1292: Chris@1292: ++counter; Chris@1292: Chris@1292: if (counter == n+1) counter = 1; Chris@1292: if (counter == 1) { Chris@1292: // this is an Nth instant, don't remove it Chris@1292: continue; Chris@1292: } Chris@1292: Chris@1293: command->deletePoint(p); Chris@1292: } Chris@1292: Chris@1292: return command->finish(); Chris@1292: } Chris@1292: Chris@305: template Chris@305: void setValue(PointType &newPoint, PointType *prevPoint = 0) { Chris@305: if (m_type == ValueFromExistingNeighbour) { Chris@305: if (!prevPoint) { Chris@305: std::cerr << "ERROR: Labeller::setValue: Previous point required but not provided" << std::endl; Chris@305: } else { Chris@305: newPoint.value = prevPoint->value; Chris@305: } Chris@305: } else { Chris@305: float value = getValueFor(newPoint, prevPoint); Chris@305: if (actingOnPrevPoint() && prevPoint) { Chris@305: prevPoint->value = value; Chris@305: } else { Chris@305: newPoint.value = value; Chris@305: } Chris@305: } Chris@305: } Chris@305: Chris@355: bool requiresPrevPoint() const { Chris@355: return (m_type == ValueFromDurationFromPrevious || Chris@355: m_type == ValueFromDurationToNext || Chris@355: m_type == ValueFromTempoFromPrevious || Chris@355: m_type == ValueFromDurationToNext); Chris@355: } Chris@355: Chris@305: bool actingOnPrevPoint() const { Chris@355: return (m_type == ValueFromDurationToNext || Chris@355: m_type == ValueFromTempoToNext); Chris@305: } Chris@305: Chris@305: protected: Chris@305: template Chris@305: float getValueFor(PointType &newPoint, PointType *prevPoint) Chris@305: { Chris@305: float value = 0.f; Chris@305: Chris@305: switch (m_type) { Chris@305: Chris@305: case ValueNone: Chris@305: value = 0; Chris@305: break; Chris@305: Chris@305: case ValueFromSimpleCounter: Chris@305: case ValueFromCyclicalCounter: Chris@1046: value = float(m_counter); Chris@305: incrementCounter(); Chris@305: break; Chris@305: Chris@305: case ValueFromTwoLevelCounter: Chris@1046: value = float(m_counter2 + double(m_counter) / double(m_dp)); Chris@305: incrementCounter(); Chris@305: break; Chris@305: Chris@305: case ValueFromFrameNumber: Chris@1046: value = float(newPoint.frame); Chris@305: break; Chris@305: Chris@305: case ValueFromRealTime: Chris@1046: if (m_rate == 0.0) { Chris@305: std::cerr << "ERROR: Labeller::getValueFor: Real-time conversion required, but no sample rate set" << std::endl; Chris@305: } else { Chris@1046: value = float(double(newPoint.frame) / m_rate); Chris@305: } Chris@305: break; Chris@305: Chris@355: case ValueFromDurationToNext: Chris@355: case ValueFromTempoToNext: Chris@355: case ValueFromDurationFromPrevious: Chris@355: case ValueFromTempoFromPrevious: Chris@1046: if (m_rate == 0.0) { Chris@305: std::cerr << "ERROR: Labeller::getValueFor: Real-time conversion required, but no sample rate set" << std::endl; Chris@305: } else if (!prevPoint) { Chris@305: std::cerr << "ERROR: Labeller::getValueFor: Time difference required, but only one point provided" << std::endl; Chris@305: } else { Chris@1046: sv_frame_t f0 = prevPoint->frame, f1 = newPoint.frame; Chris@355: if (m_type == ValueFromDurationToNext || Chris@355: m_type == ValueFromDurationFromPrevious) { Chris@1046: value = float(double(f1 - f0) / m_rate); Chris@305: } else { Chris@305: if (f1 > f0) { Chris@1046: value = float((60.0 * m_rate) / double(f1 - f0)); Chris@305: } Chris@305: } Chris@305: } Chris@305: break; Chris@305: Chris@305: case ValueFromExistingNeighbour: Chris@305: // need to deal with this in the calling function, as this Chris@305: // function must handle points that don't have values to Chris@305: // read from Chris@305: break; Chris@305: Chris@305: case ValueFromLabel: Chris@305: if (newPoint.label != "") { Chris@305: // more forgiving than QString::toFloat() Chris@1046: value = float(atof(newPoint.label.toLocal8Bit())); Chris@305: } else { Chris@305: value = 0.f; Chris@305: } Chris@305: break; Chris@305: } Chris@305: Chris@305: return value; Chris@305: } Chris@305: Chris@305: ValueType m_type; Chris@305: int m_counter; Chris@305: int m_counter2; Chris@305: int m_cycle; Chris@305: int m_dp; Chris@1046: sv_samplerate_t m_rate; Chris@305: }; Chris@305: Chris@305: #endif