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@1652: #include "base/Selection.h" Chris@1652: #include "base/Event.h" Chris@305: Chris@1652: #include "EventCommands.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@1652: // Chris@1652: // 5. generate new labelled points in the gaps between other Chris@1652: // points (subdivide), or remove them (winnow) 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@1652: enum Application { Chris@1652: AppliesToThisEvent, Chris@1652: AppliesToPreviousEvent Chris@1652: }; Chris@1652: typedef std::pair Relabelling; Chris@1652: typedef std::pair Revaluing; Chris@1652: Chris@1652: /** Chris@1652: * Return a labelled event based on the given event, previous Chris@1652: * event if supplied, and internal labeller state. The returned Chris@1652: * event replaces either the event provided or the previous event, Chris@1652: * depending on the Application value in the returned pair. Chris@1652: */ Chris@1652: Relabelling Chris@1652: label(Event e, const Event *prev = nullptr) { Chris@1652: Chris@1652: QString label = e.getLabel(); Chris@1652: Chris@305: if (m_type == ValueNone) { Chris@1652: label = ""; Chris@305: } else if (m_type == ValueFromTwoLevelCounter) { Chris@1652: 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@1652: label = tr("%1").arg(e.getFrame()); Chris@305: } else { Chris@1652: float value = getValueFor(e, prev); Chris@1652: label = QString("%1").arg(value); Chris@1652: } Chris@1652: Chris@1652: if (actingOnPrevEvent() && prev) { Chris@1652: return { AppliesToPreviousEvent, prev->withLabel(label) }; Chris@1652: } else { Chris@1652: return { AppliesToThisEvent, e.withLabel(label) }; Chris@305: } Chris@305: } Chris@1652: Chris@1652: /** Chris@1652: * Return an event with a value following the labelling scheme, Chris@1652: * based on the given event, previous event if supplied, and Chris@1652: * internal labeller state. The returned event replaces either the Chris@1652: * event provided or the previous event, depending on the Chris@1652: * Application value in the returned pair. Chris@1652: */ Chris@1652: Revaluing Chris@1652: revalue(Event e, const Event *prev = nullptr) { Chris@1652: Chris@1652: float value = e.getValue(); Chris@305: Chris@1652: if (m_type == ValueFromExistingNeighbour) { Chris@1652: if (!prev) { Chris@1652: std::cerr << "ERROR: Labeller::setValue: Previous point required but not provided" << std::endl; Chris@1652: } else { Chris@1652: return { AppliesToThisEvent, e.withValue(prev->getValue()) }; Chris@1652: } Chris@1652: } else { Chris@1652: value = getValueFor(e, prev); Chris@1652: } Chris@1652: Chris@1652: if (actingOnPrevEvent() && prev) { Chris@1652: return { AppliesToPreviousEvent, prev->withValue(value) }; Chris@1652: } else { Chris@1652: return { AppliesToThisEvent, e.withValue(value) }; Chris@1652: } Chris@1652: } Chris@1652: Chris@1291: /** Chris@1652: * Relabel all events in the given event vector that lie within Chris@1652: * the given multi-selection, according to the labelling Chris@1652: * properties of this labeller. Return a command that has been Chris@1742: * executed but not yet added to the history. The id must be that Chris@1742: * of a type that can be retrieved from the AnyById store and Chris@1742: * dynamic_cast to EventEditable. Chris@1291: */ Chris@1742: Command *labelAll(int editableId, Chris@1652: MultiSelection *ms, Chris@1652: const EventVector &allEvents) { Chris@305: Chris@1742: auto command = new ChangeEventsCommand Chris@1742: (editableId, tr("Label Points")); Chris@305: Chris@1652: Event prev; Chris@1652: bool havePrev = false; Chris@305: Chris@1652: for (auto p: allEvents) { Chris@305: Chris@305: if (ms) { Chris@1652: Selection s(ms->getContainingSelection(p.getFrame(), false)); Chris@1652: if (!s.contains(p.getFrame())) { Chris@1652: prev = p; Chris@1652: havePrev = true; Chris@1293: continue; Chris@305: } Chris@305: } Chris@305: Chris@1652: auto labelling = label(p, havePrev ? &prev : nullptr); Chris@1652: Chris@1652: if (labelling.first == AppliesToThisEvent) { Chris@1652: command->remove(p); Chris@305: } else { Chris@1652: command->remove(prev); Chris@305: } Chris@305: Chris@1652: command->add(labelling.second); Chris@1652: Chris@1652: prev = p; Chris@1652: havePrev = true; Chris@305: } Chris@305: Chris@1291: return command->finish(); Chris@1291: } Chris@1291: Chris@1291: /** Chris@1652: * For each event in the given event vector (except the last), if Chris@1652: * that event lies within the given multi-selection, add n-1 new Chris@1652: * events at equally spaced intervals between it and the following Chris@1652: * event. Return a command that has been executed but not yet Chris@1742: * added to the history. The id must be that of a type that can Chris@1742: * be retrieved from the AnyById store and dynamic_cast to Chris@1742: * EventEditable. Chris@1291: */ Chris@1742: Command *subdivide(int editableId, Chris@1652: MultiSelection *ms, Chris@1652: const EventVector &allEvents, Chris@1652: int n) { Chris@1291: Chris@1742: auto command = new ChangeEventsCommand Chris@1742: (editableId, tr("Subdivide Points")); Chris@1652: Chris@1652: for (auto i = allEvents.begin(); i != allEvents.end(); ++i) { Chris@1291: Chris@1291: auto j = i; Chris@1291: // require a "next point" even if it's not in selection Chris@1652: if (++j == allEvents.end()) { Chris@1291: break; Chris@1291: } Chris@1291: Chris@1291: if (ms) { Chris@1652: Selection s(ms->getContainingSelection(i->getFrame(), false)); Chris@1652: if (!s.contains(i->getFrame())) { Chris@1293: continue; Chris@1291: } Chris@1291: } Chris@1291: Chris@1652: Event p(*i); Chris@1652: Event 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@1652: sv_frame_t f = p.getFrame() + Chris@1652: (m * (nextP.getFrame() - p.getFrame())) / n; Chris@1652: Event newPoint = p Chris@1652: .withFrame(f) Chris@1652: .withLabel(tr("%1.%2").arg(p.getLabel()).arg(m+1)); Chris@1652: command->add(newPoint); Chris@1291: } Chris@1291: } Chris@1291: Chris@1291: return command->finish(); Chris@305: } Chris@305: Chris@1292: /** Chris@1652: * The opposite of subdivide. Given an event vector, a Chris@1652: * multi-selection, and a number n, remove all but every nth event Chris@1652: * from the vector within the extents of the multi-selection. Chris@1292: * Return a command that has been executed but not yet added to Chris@1742: * the history. The id must be that of a type that can be Chris@1742: * retrieved from the AnyById store and dynamic_cast to Chris@1742: * EventEditable. Chris@1292: */ Chris@1742: Command *winnow(int editableId, Chris@1652: MultiSelection *ms, Chris@1652: const EventVector &allEvents, Chris@1652: int n) { Chris@1652: Chris@1742: auto command = new ChangeEventsCommand Chris@1742: (editableId, tr("Winnow Points")); Chris@1292: Chris@1292: int counter = 0; Chris@1292: Chris@1652: for (auto p: allEvents) { Chris@1292: Chris@1292: if (ms) { Chris@1652: Selection s(ms->getContainingSelection(p.getFrame(), false)); Chris@1652: if (!s.contains(p.getFrame())) { 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@1652: command->remove(p); Chris@1292: } Chris@1292: Chris@1292: return command->finish(); Chris@1292: } Chris@1292: 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@1652: bool actingOnPrevEvent() const { Chris@355: return (m_type == ValueFromDurationToNext || Chris@355: m_type == ValueFromTempoToNext); Chris@305: } Chris@305: Chris@305: protected: Chris@1652: float getValueFor(Event p, const Event *prev) { Chris@1652: 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@1652: value = float(p.getFrame()); 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@1652: value = float(double(p.getFrame()) / 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@1652: } else if (!prev) { Chris@305: std::cerr << "ERROR: Labeller::getValueFor: Time difference required, but only one point provided" << std::endl; Chris@305: } else { Chris@1652: sv_frame_t f0 = prev->getFrame(), f1 = p.getFrame(); 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@1652: if (p.getLabel() != "") { Chris@305: // more forgiving than QString::toFloat() Chris@1652: value = float(atof(p.getLabel().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