changeset 1658:5b7b01da430a single-point

Start updating SparseOneDimensionalModel
author Chris Cannam
date Wed, 20 Mar 2019 16:22:13 +0000
parents 31b46a5647db
children 8bf3a52a1604
files data/fileio/CSVFileReader.cpp data/model/SparseOneDimensionalModel.h data/model/SparseTimeValueModel.h data/model/test/TestSparseModels.h rdf/RDFExporter.cpp rdf/RDFImporter.cpp transform/FeatureExtractionModelTransformer.cpp
diffstat 7 files changed, 256 insertions(+), 185 deletions(-) [+]
line wrap: on
line diff
--- a/data/fileio/CSVFileReader.cpp	Wed Mar 20 15:45:52 2019 +0000
+++ b/data/fileio/CSVFileReader.cpp	Wed Mar 20 16:22:13 2019 +0000
@@ -437,8 +437,8 @@
 
             if (modelType == CSVFormat::OneDimensionalModel) {
             
-                SparseOneDimensionalModel::Point point(frameNo, label);
-                model1->addPoint(point);
+                Event point(frameNo, label);
+                model1->add(point);
 
             } else if (modelType == CSVFormat::TwoDimensionalModel) {
 
--- a/data/model/SparseOneDimensionalModel.h	Wed Mar 20 15:45:52 2019 +0000
+++ b/data/model/SparseOneDimensionalModel.h	Wed Mar 20 16:22:13 2019 +0000
@@ -4,7 +4,6 @@
     Sonic Visualiser
     An audio file viewer and annotation editor.
     Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006 Chris Cannam.
     
     This program is free software; you can redistribute it and/or
     modify it under the terms of the GNU General Public License as
@@ -16,115 +15,173 @@
 #ifndef SV_SPARSE_ONE_DIMENSIONAL_MODEL_H
 #define SV_SPARSE_ONE_DIMENSIONAL_MODEL_H
 
-#include "SparseModel.h"
+#include "EventCommands.h"
+#include "TabularModel.h"
+#include "Model.h"
+#include "DeferredNotifier.h"
+
 #include "base/NoteData.h"
+#include "base/EventSeries.h"
 #include "base/NoteExportable.h"
 #include "base/PlayParameterRepository.h"
 #include "base/RealTime.h"
 
+#include "system/System.h"
+
 #include <QStringList>
 
-struct OneDimensionalPoint
-{
-public:
-    OneDimensionalPoint(sv_frame_t _frame) : frame(_frame) { }
-    OneDimensionalPoint(sv_frame_t _frame, QString _label) : frame(_frame), label(_label) { }
-
-    int getDimensions() const { return 1; }
-    
-    sv_frame_t frame;
-    QString label;
-
-    QString getLabel() const { return label; }
-
-    void toXml(QTextStream &stream,
-               QString indent = "",
-               QString extraAttributes = "") const
-    {
-        stream << QString("%1<point frame=\"%2\" label=\"%3\" %4/>\n")
-            .arg(indent).arg(frame).arg(XmlExportable::encodeEntities(label))
-            .arg(extraAttributes);
-    }
-
-    QString toDelimitedDataString(QString delimiter, DataExportOptions, sv_samplerate_t sampleRate) const
-    {
-        QStringList list;
-        list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str();
-        if (label != "") list << label;
-        return list.join(delimiter);
-    }
-
-    struct Comparator {
-        bool operator()(const OneDimensionalPoint &p1,
-                        const OneDimensionalPoint &p2) const {
-            if (p1.frame != p2.frame) return p1.frame < p2.frame;
-            return p1.label < p2.label;
-        }
-    };
-    
-    struct OrderComparator {
-        bool operator()(const OneDimensionalPoint &p1,
-                        const OneDimensionalPoint &p2) const {
-            return p1.frame < p2.frame;
-        }
-    };
-
-    bool operator==(const OneDimensionalPoint &p) const {
-        return (frame == p.frame && label == p.label);
-    }
-};
-
-
-class SparseOneDimensionalModel : public SparseModel<OneDimensionalPoint>,
+/**
+ * A model representing a series of time instants with optional labels
+ * but without values.
+ */
+class SparseOneDimensionalModel : public Model,
+                                  public TabularModel,
+                                  public EventEditable,
                                   public NoteExportable
 {
     Q_OBJECT
     
 public:
-    SparseOneDimensionalModel(sv_samplerate_t sampleRate, int resolution,
+    SparseOneDimensionalModel(sv_samplerate_t sampleRate,
+                              int resolution,
                               bool notifyOnAdd = true) :
-        SparseModel<OneDimensionalPoint>(sampleRate, resolution, notifyOnAdd)
-    {
+        m_sampleRate(sampleRate),
+        m_resolution(resolution),
+        m_notifier(this,
+                   notifyOnAdd ?
+                   DeferredNotifier::NOTIFY_ALWAYS :
+                   DeferredNotifier::NOTIFY_DEFERRED),
+        m_completion(100) {
         PlayParameterRepository::getInstance()->addPlayable(this);
     }
 
-    virtual ~SparseOneDimensionalModel()
-    {
+    virtual ~SparseOneDimensionalModel() {
         PlayParameterRepository::getInstance()->removePlayable(this);
     }
 
-    bool canPlay() const override { return true; }
-
-    QString getDefaultPlayClipId() const override
-    {
-        return "tap";
-    }
-
-    int getIndexOf(const Point &point)
-    {
-        // slow
-        int i = 0;
-        Point::Comparator comparator;
-        for (PointList::const_iterator j = m_points.begin();
-             j != m_points.end(); ++j, ++i) {
-            if (!comparator(*j, point) && !comparator(point, *j)) return i;
-        }
-        return -1;
-    }
-
     QString getTypeName() const override { return tr("Sparse 1-D"); }
 
+    bool isOK() const override { return true; }
+    sv_frame_t getStartFrame() const override { return m_events.getStartFrame(); }
+    sv_frame_t getEndFrame() const override { return m_events.getEndFrame(); }
+    sv_samplerate_t getSampleRate() const override { return m_sampleRate; }
+    int getResolution() const { return m_resolution; }
+
+    bool canPlay() const override { return true; }
+    QString getDefaultPlayClipId() const override { return "tap"; }
+    
+    int getCompletion() const { return m_completion; }
+
+    void setCompletion(int completion, bool update = true) {
+        
+        {   QMutexLocker locker(&m_mutex);
+            if (m_completion == completion) return;
+            m_completion = completion;
+        }
+
+        if (update) {
+            m_notifier.makeDeferredNotifications();
+        }
+        
+        emit completionChanged();
+
+        if (completion == 100) {
+            // henceforth:
+            m_notifier.switchMode(DeferredNotifier::NOTIFY_ALWAYS);
+            emit modelChanged();
+        }
+    }
+    
+    /**
+     * Query methods.
+     */
+
+    int getEventCount() const {
+        return m_events.count();
+    }
+    bool isEmpty() const {
+        return m_events.isEmpty();
+    }
+    bool containsEvent(const Event &e) const {
+        return m_events.contains(e);
+    }
+    EventVector getAllEvents() const {
+        return m_events.getAllEvents();
+    }
+    EventVector getEventsSpanning(sv_frame_t f, sv_frame_t duration) const {
+        return m_events.getEventsSpanning(f, duration);
+    }
+    EventVector getEventsCovering(sv_frame_t f) const {
+        return m_events.getEventsCovering(f);
+    }
+    EventVector getEventsWithin(sv_frame_t f, sv_frame_t duration,
+                                int overspill = 0) const {
+        return m_events.getEventsWithin(f, duration, overspill);
+    }
+    EventVector getEventsStartingWithin(sv_frame_t f, sv_frame_t duration) const {
+        return m_events.getEventsStartingWithin(f, duration);
+    }
+    EventVector getEventsStartingAt(sv_frame_t f) const {
+        return m_events.getEventsStartingAt(f);
+    }
+    bool getNearestEventMatching(sv_frame_t startSearchAt,
+                                 std::function<bool(Event)> predicate,
+                                 EventSeries::Direction direction,
+                                 Event &found) const {
+        return m_events.getNearestEventMatching
+            (startSearchAt, predicate, direction, found);
+    }
+
+    /**
+     * Editing methods.
+     */
+    void add(Event e) override {
+
+        {   QMutexLocker locker(&m_mutex);
+            m_events.add(e.withoutValue().withoutDuration());
+        }
+        
+        m_notifier.update(e.getFrame(), m_resolution);
+    }
+    
+    void remove(Event e) override {
+        {   QMutexLocker locker(&m_mutex);
+            m_events.remove(e);
+        }
+        emit modelChangedWithin(e.getFrame(), e.getFrame() + m_resolution);
+    }
+    
     /**
      * TabularModel methods.  
      */
     
-    int getColumnCount() const override
-    {
+    int getRowCount() const override {
+        return m_events.count();
+    }
+
+    int getColumnCount() const override {
         return 3;
     }
 
-    QString getHeading(int column) const override
-    {
+    bool isColumnTimeValue(int column) const override {
+        // NB duration is not a "time value" -- that's for columns
+        // whose sort ordering is exactly that of the frame time
+        return (column < 2);
+    }
+
+    sv_frame_t getFrameForRow(int row) const override {
+        if (row < 0 || row >= m_events.count()) {
+            return 0;
+        }
+        Event e = m_events.getEventByIndex(row);
+        return e.getFrame();
+    }
+
+    int getRowForFrame(sv_frame_t frame) const override {
+        return m_events.getIndexForEvent(Event(frame));
+    }
+    
+    QString getHeading(int column) const override {
         switch (column) {
         case 0: return tr("Time");
         case 1: return tr("Frame");
@@ -133,57 +190,48 @@
         }
     }
 
-    QVariant getData(int row, int column, int role) const override
-    {
-        if (column < 2) {
-            return SparseModel<OneDimensionalPoint>::getData
-                (row, column, role);
+    SortType getSortType(int column) const override {
+        if (column == 2) return SortAlphabetical;
+        return SortNumeric;
+    }
+
+    QVariant getData(int row, int column, int role) const override {
+        
+        if (row < 0 || row >= m_events.count()) {
+            return QVariant();
         }
 
-        PointListConstIterator i = getPointListIteratorForRow(row);
-        if (i == m_points.end()) return QVariant();
+        Event e = m_events.getEventByIndex(row);
 
         switch (column) {
-        case 2: return i->label;
+        case 0: return adaptFrameForRole(e.getFrame(), getSampleRate(), role);
+        case 1: return int(e.getFrame());
+        case 2: return e.getLabel();
         default: return QVariant();
         }
     }
 
-    Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override
-    {
-        if (column < 2) {
-            return SparseModel<OneDimensionalPoint>::getSetDataCommand
-                (row, column, value, role);
+    Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override {
+        if (row < 0 || row >= m_events.count()) return nullptr;
+        if (role != Qt::EditRole) return nullptr;
+
+        Event e0 = m_events.getEventByIndex(row);
+        Event e1;
+
+        switch (column) {
+        case 0: e1 = e0.withFrame(sv_frame_t(round(value.toDouble() *
+                                                   getSampleRate()))); break;
+        case 1: e1 = e0.withFrame(value.toInt()); break;
+        case 2: e1 = e0.withLabel(value.toString()); break;
         }
 
-        if (role != Qt::EditRole) return 0;
-        PointListConstIterator i = getPointListIteratorForRow(row);
-        if (i == m_points.end()) return 0;
-        EditCommand *command = new EditCommand(this, tr("Edit Data"));
-
-        Point point(*i);
-        command->deletePoint(point);
-
-        switch (column) {
-        case 2: point.label = value.toString(); break;
-        }
-
-        command->addPoint(point);
+        ChangeEventsCommand *command =
+            new ChangeEventsCommand(this, tr("Edit Data"));
+        command->remove(e0);
+        command->add(e1);
         return command->finish();
     }
 
-
-    bool isColumnTimeValue(int column) const override
-    {
-        return (column < 2); 
-    }
-
-    SortType getSortType(int column) const override
-    {
-        if (column == 2) return SortAlphabetical;
-        return SortNumeric;
-    }
-
     /**
      * NoteExportable methods.
      */
@@ -200,21 +248,45 @@
     NoteList getNotesStartingWithin(sv_frame_t startFrame,
                                     sv_frame_t duration) const override {
         
-        PointList points = getPoints(startFrame, startFrame + duration);
         NoteList notes;
-
-        for (PointList::iterator pli =
-                 points.begin(); pli != points.end(); ++pli) {
-
-            notes.push_back
-                (NoteData(pli->frame,
-                          sv_frame_t(getSampleRate() / 6), // arbitrary short duration
-                          64,   // default pitch
-                          100)); // default velocity
+        EventVector ee = m_events.getEventsStartingWithin(startFrame, duration);
+        for (const auto &e: ee) {
+            notes.push_back(e.toNoteData(getSampleRate(), true));
         }
-
         return notes;
     }
+    
+    /**
+     * XmlExportable methods.
+     */
+    void toXml(QTextStream &out,
+               QString indent = "",
+               QString extraAttributes = "") const override {
+
+        Model::toXml
+            (out,
+             indent,
+             QString("type=\"sparse\" dimensions=\"1\" resolution=\"%1\" "
+                     "notifyOnAdd=\"%2\" dataset=\"%3\" %4")
+             .arg(m_resolution)
+             .arg("true") // always true after model reaches 100% -
+                          // subsequent events are always notified
+             .arg(getObjectExportId(&m_events))
+             .arg(extraAttributes));
+        
+        m_events.toXml(out, indent, QString("dimensions=\"1\""));
+    }
+    
+protected:
+    sv_samplerate_t m_sampleRate;
+    int m_resolution;
+
+    DeferredNotifier m_notifier;
+    int m_completion;
+
+    EventSeries m_events;
+
+    mutable QMutex m_mutex;  
 };
 
 #endif
--- a/data/model/SparseTimeValueModel.h	Wed Mar 20 15:45:52 2019 +0000
+++ b/data/model/SparseTimeValueModel.h	Wed Mar 20 16:22:13 2019 +0000
@@ -171,8 +171,7 @@
 
         bool allChange = false;
            
-        {
-            QMutexLocker locker(&m_mutex);
+        {   QMutexLocker locker(&m_mutex);
             m_events.add(e.withoutDuration()); // can't have duration here
 
             if (e.getLabel() != "") {
--- a/data/model/test/TestSparseModels.h	Wed Mar 20 15:45:52 2019 +0000
+++ b/data/model/test/TestSparseModels.h	Wed Mar 20 16:22:13 2019 +0000
@@ -33,76 +33,78 @@
     void s1d_empty() {
         SparseOneDimensionalModel m(100, 10, false);
         QCOMPARE(m.isEmpty(), true);
-        QCOMPARE(m.getPointCount(), 0);
-        QCOMPARE(m.getPoints().begin(), m.getPoints().end());
+        QCOMPARE(m.getEventCount(), 0);
+        QCOMPARE(m.getAllEvents().size(), 0);
         QCOMPARE(m.getStartFrame(), 0);
         QCOMPARE(m.getEndFrame(), 0);
         QCOMPARE(m.getSampleRate(), 100);
         QCOMPARE(m.getResolution(), 10);
         QCOMPARE(m.isSparse(), true);
 
-        SparseOneDimensionalModel::Point p(10);
-        m.addPoint(p);
+        Event p(10);
+        m.add(p);
+/*!!!
         m.clear();
         QCOMPARE(m.isEmpty(), true);
-        QCOMPARE(m.getPointCount(), 0);
-        QCOMPARE(m.getPoints().begin(), m.getPoints().end());
+        QCOMPARE(m.getEventCount(), 0);
+        QCOMPARE(m.getAllEvents().size(), 0);
         QCOMPARE(m.getStartFrame(), 0);
         QCOMPARE(m.getEndFrame(), 0);
 
-        m.addPoint(p);
-        m.deletePoint(p);
+        m.add(p);
+*/
+        m.remove(p);
         QCOMPARE(m.isEmpty(), true);
-        QCOMPARE(m.getPointCount(), 0);
-        QCOMPARE(m.getPoints().begin(), m.getPoints().end());
+        QCOMPARE(m.getEventCount(), 0);
+        QCOMPARE(m.getAllEvents().size(), 0);
         QCOMPARE(m.getStartFrame(), 0);
         QCOMPARE(m.getEndFrame(), 0);
     }
 
     void s1d_extents() {
         SparseOneDimensionalModel m(100, 10, false);
-        SparseOneDimensionalModel::Point p1(20);
-        m.addPoint(p1);
+        Event p1(20);
+        m.add(p1);
         QCOMPARE(m.isEmpty(), false);
-        QCOMPARE(m.getPointCount(), 1);
-        SparseOneDimensionalModel::Point p2(50);
-        m.addPoint(p2);
+        QCOMPARE(m.getEventCount(), 1);
+        Event p2(50);
+        m.add(p2);
         QCOMPARE(m.isEmpty(), false);
-        QCOMPARE(m.getPointCount(), 2);
-        QCOMPARE(m.getPoints().size(), 2);
-        QCOMPARE(*m.getPoints().begin(), p1);
-        QCOMPARE(*m.getPoints().rbegin(), p2);
+        QCOMPARE(m.getEventCount(), 2);
+        QCOMPARE(m.getAllEvents().size(), 2);
+        QCOMPARE(*m.getAllEvents().begin(), p1);
+        QCOMPARE(*m.getAllEvents().rbegin(), p2);
         QCOMPARE(m.getStartFrame(), 20);
         QCOMPARE(m.getEndFrame(), 60);
-        QCOMPARE(m.containsPoint(p1), true);
-        m.deletePoint(p1);
-        QCOMPARE(m.getPointCount(), 1);
-        QCOMPARE(m.getPoints().size(), 1);
-        QCOMPARE(*m.getPoints().begin(), p2);
+        QCOMPARE(m.containsEvent(p1), true);
+        m.remove(p1);
+        QCOMPARE(m.getEventCount(), 1);
+        QCOMPARE(m.getAllEvents().size(), 1);
+        QCOMPARE(*m.getAllEvents().begin(), p2);
         QCOMPARE(m.getStartFrame(), 50);
         QCOMPARE(m.getEndFrame(), 60);
-        QCOMPARE(m.containsPoint(p1), false);
+        QCOMPARE(m.containsEvent(p1), false);
     }
              
     void s1d_sample() {
         SparseOneDimensionalModel m(100, 10, false);
-        SparseOneDimensionalModel::Point p1(20), p2(20), p3(50);
-        m.addPoint(p1);
-        m.addPoint(p2);
-        m.addPoint(p3);
-        QCOMPARE(m.getPoints().size(), 3);
-        QCOMPARE(*m.getPoints().begin(), p1);
-        QCOMPARE(*m.getPoints().rbegin(), p3);
+        Event p1(20), p2(20), p3(50);
+        m.add(p1);
+        m.add(p2);
+        m.add(p3);
+        QCOMPARE(m.getAllEvents().size(), 3);
+        QCOMPARE(*m.getAllEvents().begin(), p1);
+        QCOMPARE(*m.getAllEvents().rbegin(), p3);
 /*!!!
-        auto pp = m.getPoints(20, 30);
+        auto pp = m.getAllEvents(20, 30);
         QCOMPARE(pp.size(), 2);
         QCOMPARE(*pp.begin(), p1);
         QCOMPARE(*pp.rbegin(), p2);
         
-        pp = m.getPoints(40, 50);
+        pp = m.getAllEvents(40, 50);
         QCOMPARE(pp.size(), 0);
 
-        pp = m.getPoints(50, 50);
+        pp = m.getAllEvents(50, 50);
         QCOMPARE(pp.size(), 1);
         QCOMPARE(*pp.begin(), p3);
 */
@@ -111,17 +113,17 @@
     void s1d_xml() {
         SparseOneDimensionalModel m(100, 10, false);
         m.setObjectName("This \"&\" that");
-        SparseOneDimensionalModel::Point p1(20), p2(20), p3(50);
-        p2.label = "Label &'\">";
-        m.addPoint(p1);
-        m.addPoint(p2);
-        m.addPoint(p3);
+        Event p1(20), p2(20), p3(50);
+        p2 = p2.withLabel("Label &'\">");
+        m.add(p1);
+        m.add(p2);
+        m.add(p3);
         QString xml;
         QTextStream str(&xml, QIODevice::WriteOnly);
         m.toXml(str);
         str.flush();
         QString expected =
-            "<model id='1' name='This &quot;&amp;&quot; that' sampleRate='100' start='20' end='60' type='sparse' dimensions='1' resolution='10' notifyOnAdd='false' dataset='0' />\n"
+            "<model id='1' name='This &quot;&amp;&quot; that' sampleRate='100' start='20' end='60' type='sparse' dimensions='1' resolution='10' notifyOnAdd='true' dataset='0' />\n"
             "<dataset id='0' dimensions='1'>\n"
             "  <point frame='20' label='' />\n"
             "  <point frame='20' label='Label &amp;&apos;&quot;&gt;' />\n"
--- a/rdf/RDFExporter.cpp	Wed Mar 20 15:45:52 2019 +0000
+++ b/rdf/RDFExporter.cpp	Wed Mar 20 16:22:13 2019 +0000
@@ -120,12 +120,11 @@
         if (m) {
             f.hasTimestamp = true;
             f.hasDuration = false;
-            const SparseOneDimensionalModel::PointList &pl(m->getPoints());
-            for (SparseOneDimensionalModel::PointList::const_iterator i = pl.begin(); 
-                 i != pl.end(); ++i) {
-                f.timestamp = RealTime::frame2RealTime(i->frame, sr).toVampRealTime();
+            EventVector ee(m->getAllEvents());
+            for (auto e: ee) {
+                f.timestamp = RealTime::frame2RealTime(e.getFrame(), sr).toVampRealTime();
                 f.values.clear();
-                f.label = i->label.toStdString();
+                f.label = e.getLabel().toStdString();
                 m_fw->write(trackId, transform, output, features, summaryType);
             }
             return;
--- a/rdf/RDFImporter.cpp	Wed Mar 20 15:45:52 2019 +0000
+++ b/rdf/RDFImporter.cpp	Wed Mar 20 16:22:13 2019 +0000
@@ -709,8 +709,8 @@
     SparseOneDimensionalModel *sodm =
         dynamic_cast<SparseOneDimensionalModel *>(model);
     if (sodm) {
-        SparseOneDimensionalModel::Point point(ftime, label);
-        sodm->addPoint(point);
+        Event point(ftime, label);
+        sodm->add(point);
         return;
     }
 
--- a/transform/FeatureExtractionModelTransformer.cpp	Wed Mar 20 15:45:52 2019 +0000
+++ b/transform/FeatureExtractionModelTransformer.cpp	Wed Mar 20 16:22:13 2019 +0000
@@ -977,8 +977,7 @@
             getConformingOutput<SparseOneDimensionalModel>(n);
         if (!model) return;
 
-        model->addPoint(SparseOneDimensionalModel::Point
-                       (frame, feature.label.c_str()));
+        model->add(Event(frame, feature.label.c_str()));
         
     } else if (isOutput<SparseTimeValueModel>(n)) {