Mercurial > hg > svcore
diff data/model/Labeller.h @ 305:fc656505c573
* Add labelling option for instants inserted through tapping (closes FR#1674184)
Needs some refinement still, but it's almost functionally complete
author | Chris Cannam |
---|---|
date | Mon, 08 Oct 2007 14:44:38 +0000 |
parents | |
children | 4669894a89ad |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/Labeller.h Mon Oct 08 14:44:38 2007 +0000 @@ -0,0 +1,296 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006-2007 Chris Cannam and QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _LABELLER_H_ +#define _LABELLER_H_ + +#include "SparseModel.h" +#include "SparseValueModel.h" + +#include "base/Selection.h" + +#include <QObject> + +#include <map> +#include <iostream> + +class Labeller : public QObject +{ +public: + enum ValueType { + ValueNone, + ValueFromSimpleCounter, + ValueFromCyclicalCounter, + ValueFromTwoLevelCounter, + ValueFromFrameNumber, + ValueFromRealTime, + ValueFromRealTimeDifference, + ValueFromTempo, + ValueFromExistingNeighbour, + ValueFromLabel + }; + + // uses: + // + // 1. when adding points to a time-value model, generate values + // for those points based on their times or labels or a counter + // + // 2. when adding a single point to a time-instant model, generate + // a label for it based on its time and that of the previous point + // or a counter + // + // 3. when adding a single point to a time-instant model, generate + // a label for the previous point based on its time and that of + // the point just added (as tempo is based on time to the next + // point, not the previous one) + // + // 4. re-label a set of points that have already been added to a + // model + + Labeller(ValueType type) : + m_type(type), + m_counter(1), + m_counter2(1), + m_cycle(4), + m_dp(10), + m_rate(0) { } + + Labeller(const Labeller &l) : + QObject(), + m_type(l.m_type), + m_counter(1), + m_counter2(1), + m_cycle(l.m_cycle), + m_dp(l.m_dp), + m_rate(l.m_rate) { } + + virtual ~Labeller() { } + + typedef std::map<ValueType, QString> TypeNameMap; + TypeNameMap getTypeNames() const { + TypeNameMap m; + m[ValueNone] = tr("No numbering"); + m[ValueFromSimpleCounter] = tr("Simple counter starting from 1"); + m[ValueFromCyclicalCounter] = tr("Cyclical counter starting from 1"); + m[ValueFromTwoLevelCounter] = tr("Cyclical two-level counter (bar/beat)"); + m[ValueFromFrameNumber] = tr("Audio sample frame number"); + m[ValueFromRealTime] = tr("Time in seconds"); + m[ValueFromRealTimeDifference] = tr("Duration to the following item"); + m[ValueFromTempo] = tr("Tempo (bpm) based on duration to following item"); + m[ValueFromExistingNeighbour] = tr("Same value as the nearest previous item"); + m[ValueFromLabel] = tr("Value extracted from the item's label (where possible)"); + return m; + } + + ValueType getType() const { return m_type; } + void setType(ValueType type) { m_type = type; } + + int getCounterValue() const { return m_counter; } + void setCounterValue(int v) { m_counter = v; } + + int getSecondLevelCounterValue() const { return m_counter2; } + void setSecondLevelCounterValue(int v) { m_counter2 = v; } + + int getCounterCycleSize() const { return m_cycle; } + void setCounterCycleSize(int s) { + m_cycle = s; + m_dp = 1; + while (s > 0) { + s /= 10; + m_dp *= 10; + } + } + + void setSampleRate(float rate) { m_rate = rate; } + + void incrementCounter() { + m_counter++; + if (m_type == ValueFromCyclicalCounter || + m_type == ValueFromTwoLevelCounter) { + if (m_counter > m_cycle) { + m_counter = 1; + m_counter2++; + } + } + } + + template <typename PointType> + void label(PointType &newPoint, PointType *prevPoint = 0) { + if (m_type == ValueNone) { + newPoint.label = ""; + } else if (m_type == ValueFromTwoLevelCounter) { + newPoint.label = tr("%1.%2").arg(m_counter2).arg(m_counter); + incrementCounter(); + } else { + float value = getValueFor<PointType>(newPoint, prevPoint); + if (actingOnPrevPoint() && prevPoint) { + prevPoint->label = QString("%1").arg(value); + } else { + newPoint.label = QString("%1").arg(value); + } + } + } + + template <typename PointType> + void labelAll(SparseModel<PointType> &model, MultiSelection *ms) { + + typename SparseModel<PointType>::PointList::iterator i; + typename SparseModel<PointType>::PointList pl(model.getPoints()); + + typename SparseModel<PointType>::EditCommand *command = + new typename SparseModel<PointType>::EditCommand + (&model, tr("Label Points")); + + PointType prevPoint(0); + + for (i = pl.begin(); i != pl.end(); ++i) { + + bool inRange = true; + if (ms) { + Selection s(ms->getContainingSelection(i->frame, false)); + if (s.isEmpty() || !s.contains(i->frame)) { + inRange = false; + } + } + + PointType p(*i); + + if (!inRange) { + prevPoint = p; + continue; + } + + if (actingOnPrevPoint()) { + if (i != pl.begin()) { + command->deletePoint(prevPoint); + label<PointType>(p, &prevPoint); + command->addPoint(prevPoint); + } + } else { + command->deletePoint(p); + label<PointType>(p, &prevPoint); + command->addPoint(p); + } + + prevPoint = p; + } + + command->finish(); + } + + template <typename PointType> + void setValue(PointType &newPoint, PointType *prevPoint = 0) { + if (m_type == ValueFromExistingNeighbour) { + if (!prevPoint) { + std::cerr << "ERROR: Labeller::setValue: Previous point required but not provided" << std::endl; + } else { + newPoint.value = prevPoint->value; + } + } else { + float value = getValueFor<PointType>(newPoint, prevPoint); + if (actingOnPrevPoint() && prevPoint) { + prevPoint->value = value; + } else { + newPoint.value = value; + } + } + } + + bool actingOnPrevPoint() const { + return (m_type == ValueFromRealTimeDifference || + m_type == ValueFromTempo); + } + +protected: + template <typename PointType> + float getValueFor(PointType &newPoint, PointType *prevPoint) + { + float value = 0.f; + + switch (m_type) { + + case ValueNone: + value = 0; + break; + + case ValueFromSimpleCounter: + case ValueFromCyclicalCounter: + value = m_counter; + incrementCounter(); + break; + + case ValueFromTwoLevelCounter: + value = m_counter2 + double(m_counter) / double(m_dp); + incrementCounter(); + break; + + case ValueFromFrameNumber: + value = newPoint.frame; + break; + + case ValueFromRealTime: + if (m_rate == 0.f) { + std::cerr << "ERROR: Labeller::getValueFor: Real-time conversion required, but no sample rate set" << std::endl; + } else { + value = float(newPoint.frame) / float(m_rate); + } + break; + + case ValueFromRealTimeDifference: + case ValueFromTempo: + if (m_rate == 0.f) { + std::cerr << "ERROR: Labeller::getValueFor: Real-time conversion required, but no sample rate set" << std::endl; + } else if (!prevPoint) { + std::cerr << "ERROR: Labeller::getValueFor: Time difference required, but only one point provided" << std::endl; + } else { + size_t f0 = prevPoint->frame, f1 = newPoint.frame; + if (m_type == ValueFromRealTimeDifference) { + value = float(f1 - f0) / m_rate; + } else { + if (f1 > f0) { + value = (60.f * m_rate) / (f1 - f0); + } + } + } + break; + + case ValueFromExistingNeighbour: + // need to deal with this in the calling function, as this + // function must handle points that don't have values to + // read from + break; + + case ValueFromLabel: + if (newPoint.label != "") { + // more forgiving than QString::toFloat() + value = atof(newPoint.label.toLocal8Bit()); + } else { + value = 0.f; + } + break; + } + + return value; + } + + ValueType m_type; + int m_counter; + int m_counter2; + int m_cycle; + int m_dp; + float m_rate; +}; + +#endif