changeset 1614:2e14a7876945 single-point

Fixes and tests for PointSeries
author Chris Cannam
date Thu, 07 Mar 2019 14:35:57 +0000
parents ea4f3593c39c
children 24dc8cb42755
files base/PointSeries.h base/test/TestPointSeries.h data/model/SparseModel.h data/model/test/TestSparseModels.h
diffstat 4 files changed, 270 insertions(+), 39 deletions(-) [+]
line wrap: on
line diff
--- a/base/PointSeries.h	Wed Mar 06 16:37:10 2019 +0000
+++ b/base/PointSeries.h	Thu Mar 07 14:35:57 2019 +0000
@@ -19,6 +19,8 @@
 
 #include <set>
 
+//#define DEBUG_POINT_SERIES 1
+
 class PointSeries
 {
 public:
@@ -33,26 +35,28 @@
             sv_frame_t frame = p.getFrame();
             sv_frame_t endFrame = p.getFrame() + p.getDuration();
 
-            std::set<Point> active;
-            auto itr = m_seams.lower_bound(frame);
-            if (itr == m_seams.end() || itr->first > frame) {
-                if (itr != m_seams.begin()) {
-                    --itr;
+            createSeam(frame);
+            createSeam(endFrame);
+            
+            auto i0 = m_seams.find(frame); // must succeed after createSeam
+            auto i1 = m_seams.find(endFrame); // likewise
+
+            for (auto i = i0; i != i1; ++i) {
+                if (i == m_seams.end()) {
+                    SVCERR << "ERROR: PointSeries::add: "
+                           << "reached end of seam map"
+                           << endl;
+                    break;
                 }
+                i->second.insert(p);
             }
-            if (itr != m_seams.end()) {
-                active = itr->second;
-            }
-            active.insert(p);
-            m_seams[frame] = active;
+        }
 
-            for (itr = m_seams.find(frame); itr->first < endFrame; ++itr) {
-                active = itr->second;
-                itr->second.insert(p);
-            }
-
-            m_seams[endFrame] = active;
-        }
+#ifdef DEBUG_POINT_SERIES
+        std::cerr << "after add:" << std::endl;
+        dumpPoints();
+        dumpSeams();
+#endif
     }
 
     void remove(const Point &p) {
@@ -72,17 +76,32 @@
             sv_frame_t frame = p.getFrame();
             sv_frame_t endFrame = p.getFrame() + p.getDuration();
 
-            auto itr = m_seams.find(frame);
-            if (itr == m_seams.end()) {
-                SVCERR << "WARNING: PointSeries::remove: frame " << frame
+            auto i0 = m_seams.find(frame);
+            auto i1 = m_seams.find(endFrame);
+
+#ifdef DEBUG_POINT_SERIES
+            // This should be impossible if we found p in m_points above
+            if (i0 == m_seams.end() || i1 == m_seams.end()) {
+                SVCERR << "ERROR: PointSeries::remove: either frame " << frame
+                       << " or endFrame " << endFrame
                        << " for point not found in seam map: point is "
                        << p.toXmlString() << endl;
-                return;
             }
+#endif
 
-            while (itr != m_seams.end() && itr->first <= endFrame) {
-                itr->second.erase(p);
-                ++itr;
+            for (auto i = i0; i != i1; ++i) {
+                if (i == m_seams.end()) {
+                    SVCERR << "ERROR: PointSeries::remove: "
+                           << "reached end of seam map"
+                           << endl;
+                    break;
+                }
+                // Can't just erase(p) as that would erase all of
+                // them, if there are several identical ones
+                auto si = i->second.find(p);
+                if (si != i->second.end()) {
+                    i->second.erase(si);
+                }
             }
 
             // Shall we "garbage-collect" here? We could be leaving
@@ -91,6 +110,12 @@
             // slow us down. But a lot depends on whether callers ever
             // really delete anything much.
         }
+
+#ifdef DEBUG_POINT_SERIES
+        std::cerr << "after remove:" << std::endl;
+        dumpPoints();
+        dumpSeams();
+#endif
     }
 
     bool contains(const Point &p) {
@@ -118,14 +143,86 @@
      * equal to it and its end frame (start + duration) is greater
      * than it.
      */
-    PointVector getPointsSpanning(sv_frame_t frame) {
-        return {};
+    PointVector getPointsSpanning(sv_frame_t frame) const {
+        PointVector span;
+
+        // first find any zero-duration points
+        auto pitr = m_points.lower_bound(Point(frame, QString()));
+        if (pitr != m_points.end()) {
+            while (pitr->getFrame() == frame) {
+                if (!pitr->haveDuration()) {
+                    span.push_back(*pitr);
+                }
+                ++pitr;
+            }
+        }
+
+        // now any non-zero-duration ones from the seam map
+        auto sitr = m_seams.lower_bound(frame);
+        if (sitr == m_seams.end() || sitr->first > frame) {
+            if (sitr != m_seams.begin()) {
+                --sitr;
+            }                
+        }
+        if (sitr != m_seams.end() && sitr->first <= frame) {
+            for (auto p: sitr->second) {
+                span.push_back(p);
+            }
+        }
+        
+        return span;
     }
 
 private:
     int m_count;
-    std::multiset<Point> m_points;
-    std::map<sv_frame_t, std::set<Point>> m_seams;
+
+    typedef std::multiset<Point> PointMultiset;
+    PointMultiset m_points;
+
+    typedef std::map<sv_frame_t, std::multiset<Point>> FramePointsMap;
+    FramePointsMap m_seams;
+
+    /** Create a seam at the given frame, copying from the prior seam
+     *  if there is one. If a seam already exists at the given frame,
+     *  leave it untouched.
+     */
+    void createSeam(sv_frame_t frame) {
+        auto itr = m_seams.lower_bound(frame);
+        if (itr == m_seams.end() || itr->first > frame) {
+            if (itr != m_seams.begin()) {
+                --itr;
+            }
+        }
+        if (itr == m_seams.end()) {
+            m_seams[frame] = {};
+        } else if (itr->first < frame) {
+            m_seams[frame] = itr->second;
+        } else if (itr->first > frame) { // itr must be begin()
+            m_seams[frame] = {};
+        }
+    }
+
+#ifdef DEBUG_POINT_SERIES
+    void dumpPoints() const {
+        std::cerr << "POINTS [" << std::endl;
+        for (const auto &p: m_points) {
+            std::cerr << p.toXmlString("  ");
+        }
+        std::cerr << "]" << std::endl;
+    }
+    
+    void dumpSeams() const {
+        std::cerr << "SEAMS [" << std::endl;
+        for (const auto &s: m_seams) {
+            std::cerr << "  " << s.first << " -> {" << std::endl;
+            for (const auto &p: s.second) {
+                std::cerr << p.toXmlString("    ");
+            }
+            std::cerr << "  }" << std::endl;
+        }
+        std::cerr << "]" << std::endl;
+    }
+#endif
 };
 
 #endif
--- a/base/test/TestPointSeries.h	Wed Mar 06 16:37:10 2019 +0000
+++ b/base/test/TestPointSeries.h	Thu Mar 07 14:35:57 2019 +0000
@@ -79,7 +79,143 @@
         QCOMPARE(s.getPointsSpanning(30), PointVector());
         QCOMPARE(s.getPointsSpanning(9), PointVector());
     }
-        
+
+    void identicalPointsSpan() {
+
+        PointSeries s;
+        Point p(10, QString());
+        s.add(p);
+        s.add(p);
+
+        PointVector span;
+        span.push_back(p);
+        span.push_back(p);
+        QCOMPARE(s.getPointsSpanning(10), span);
+        QCOMPARE(s.getPointsSpanning(11), PointVector());
+        QCOMPARE(s.getPointsSpanning(9), PointVector());
+
+        s.remove(p);
+        span.clear();
+        span.push_back(p);
+        QCOMPARE(s.getPointsSpanning(10), span);
+        QCOMPARE(s.getPointsSpanning(11), PointVector());
+        QCOMPARE(s.getPointsSpanning(9), PointVector());
+    }
+    
+    void identicalPointsWithDurationSpan() {
+
+        PointSeries s;
+        Point p(10, 1.0, 20, QString());
+        s.add(p);
+        s.add(p);
+        PointVector span;
+        span.push_back(p);
+        span.push_back(p);
+        QCOMPARE(s.getPointsSpanning(10), span);
+        QCOMPARE(s.getPointsSpanning(11), span);
+        QCOMPARE(s.getPointsSpanning(29), span);
+        QCOMPARE(s.getPointsSpanning(30), PointVector());
+        QCOMPARE(s.getPointsSpanning(9), PointVector());
+
+        s.remove(p);
+        span.clear();
+        span.push_back(p);
+        QCOMPARE(s.getPointsSpanning(10), span);
+        QCOMPARE(s.getPointsSpanning(11), span);
+        QCOMPARE(s.getPointsSpanning(29), span);
+        QCOMPARE(s.getPointsSpanning(30), PointVector());
+        QCOMPARE(s.getPointsSpanning(9), PointVector());
+    }
+
+    void multiplePointsSpan() {
+
+        PointSeries s;
+        Point a(10, QString("a"));
+        Point b(11, QString("b"));
+        Point c(40, QString("c"));
+        s.add(c);
+        s.add(a);
+        s.add(b);
+        s.remove(a);
+        s.add(a);
+        s.add(c);
+        s.remove(c);
+        QCOMPARE(s.count(), 3);
+        PointVector span;
+        span.push_back(a);
+        QCOMPARE(s.getPointsSpanning(10), span);
+        span.clear();
+        span.push_back(c);
+        QCOMPARE(s.getPointsSpanning(40), span);
+        QCOMPARE(s.getPointsSpanning(9), PointVector());
+    }
+
+    void disjointPointsWithDurationSpan() {
+
+        PointSeries s;
+        Point a(10, 1.0f, 20, QString("a"));
+        Point b(100, 1.2f, 30, QString("b"));
+        s.add(a);
+        s.add(b);
+        QCOMPARE(s.getPointsSpanning(0), PointVector());
+        QCOMPARE(s.getPointsSpanning(10), PointVector({ a }));
+        QCOMPARE(s.getPointsSpanning(15), PointVector({ a }));
+        QCOMPARE(s.getPointsSpanning(30), PointVector());
+        QCOMPARE(s.getPointsSpanning(99), PointVector());
+        QCOMPARE(s.getPointsSpanning(100), PointVector({ b }));
+        QCOMPARE(s.getPointsSpanning(120), PointVector({ b }));
+        QCOMPARE(s.getPointsSpanning(130), PointVector());
+    }
+    
+    void overlappingPointsWithAndWithoutDurationSpan() {
+
+        PointSeries s;
+        Point p(20, QString("p"));
+        Point a(10, 1.0, 20, QString("a"));
+        s.add(p);
+        s.add(a);
+        PointVector span;
+        span.push_back(a);
+        QCOMPARE(s.getPointsSpanning(15), span);
+        QCOMPARE(s.getPointsSpanning(25), span);
+        span.clear();
+        span.push_back(p);
+        span.push_back(a);
+        QCOMPARE(s.getPointsSpanning(20), span);
+    }
+
+    void overlappingPointsWithDurationSpan() {
+
+        PointSeries s;
+        Point a(20, 1.0, 10, QString("a"));
+        Point b(10, 1.0, 20, QString("b"));
+        Point c(10, 1.0, 40, QString("c"));
+        s.add(a);
+        s.add(b);
+        s.add(c);
+        QCOMPARE(s.getPointsSpanning(10), PointVector({ b, c }));
+        QCOMPARE(s.getPointsSpanning(20), PointVector({ b, c, a }));
+        QCOMPARE(s.getPointsSpanning(25), PointVector({ b, c, a }));
+        QCOMPARE(s.getPointsSpanning(30), PointVector({ c }));
+        QCOMPARE(s.getPointsSpanning(40), PointVector({ c }));
+        QCOMPARE(s.getPointsSpanning(50), PointVector());
+    }
+
+    void pointPatternSpan() {
+
+        PointSeries s;
+        Point a(0, 1.0, 18, QString("a"));
+        Point b(3, 2.0, 6, QString("b"));
+        Point c(5, 3.0, 2, QString("c"));
+        Point d(6, 4.0, 10, QString("d"));
+        Point e(14, 5.0, 3, QString("e"));
+        s.add(b);
+        s.add(c);
+        s.add(d);
+        s.add(a);
+        s.add(e);
+        QCOMPARE(s.getPointsSpanning(8), PointVector({ a, b, d }));
+    }
 };
 
 #endif
--- a/data/model/SparseModel.h	Wed Mar 06 16:37:10 2019 +0000
+++ b/data/model/SparseModel.h	Thu Mar 07 14:35:57 2019 +0000
@@ -34,8 +34,6 @@
 #include <QMutex>
 #include <QTextStream>
 
-#include "base/PointSeries.h" //!!!
-
 /**
  * Model containing sparse data (points with some properties).  The
  * properties depend on the point type.
--- a/data/model/test/TestSparseModels.h	Wed Mar 06 16:37:10 2019 +0000
+++ b/data/model/test/TestSparseModels.h	Thu Mar 07 14:35:57 2019 +0000
@@ -136,11 +136,11 @@
 
     void note_extents() {
         NoteModel m(100, 10, false);
-        NoteModel::Point p1(20, 123.4, 40, 0.8, "note 1");
+        NoteModel::Point p1(20, 123.4f, 40, 0.8f, "note 1");
         m.addPoint(p1);
         QCOMPARE(m.isEmpty(), false);
         QCOMPARE(m.getPointCount(), 1);
-        NoteModel::Point p2(50, 124.3, 30, 0.9, "note 2");
+        NoteModel::Point p2(50, 124.3f, 30, 0.9f, "note 2");
         m.addPoint(p2);
         QCOMPARE(m.isEmpty(), false);
         QCOMPARE(m.getPointCount(), 2);
@@ -163,9 +163,9 @@
              
     void note_sample() {
         NoteModel m(100, 10, false);
-        NoteModel::Point p1(20, 123.4, 20, 0.8, "note 1");
-        NoteModel::Point p2(20, 124.3, 10, 0.9, "note 2");
-        NoteModel::Point p3(50, 126.3, 30, 0.9, "note 3");
+        NoteModel::Point p1(20, 123.4f, 20, 0.8f, "note 1");
+        NoteModel::Point p2(20, 124.3f, 10, 0.9f, "note 2");
+        NoteModel::Point p3(50, 126.3f, 30, 0.9f, "note 3");
         m.addPoint(p1);
         m.addPoint(p2);
         m.addPoint(p3);
@@ -193,9 +193,9 @@
 
     void note_xml() {
         NoteModel m(100, 10, false);
-        NoteModel::Point p1(20, 123.4, 20, 0.8, "note 1");
-        NoteModel::Point p2(20, 124.3, 10, 0.9, "note 2");
-        NoteModel::Point p3(50, 126.3, 30, 0.9, "note 3");
+        NoteModel::Point p1(20, 123.4f, 20, 0.8f, "note 1");
+        NoteModel::Point p2(20, 124.3f, 10, 0.9f, "note 2");
+        NoteModel::Point p3(50, 126.3f, 30, 0.9f, "note 3");
         m.setScaleUnits("Hz");
         m.addPoint(p1);
         m.addPoint(p2);