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