diff layer/TimeValueLayer.cpp @ 1459:42c87368287c

Merge from branch single-point
author Chris Cannam
date Fri, 17 May 2019 10:02:52 +0100
parents 9abddbd57667
children 696e569ff21b
line wrap: on
line diff
--- a/layer/TimeValueLayer.cpp	Wed Apr 24 11:29:53 2019 +0100
+++ b/layer/TimeValueLayer.cpp	Fri May 17 10:02:52 2019 +0100
@@ -531,60 +531,68 @@
     return mapper;
 }
 
-SparseTimeValueModel::PointList
+EventVector
 TimeValueLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
 {
-    if (!m_model) return SparseTimeValueModel::PointList();
+    if (!m_model) return {};
 
+    // Return all points at a frame f, where f is the closest frame to
+    // pixel coordinate x whose pixel coordinate is both within a
+    // small (but somewhat arbitrary) fuzz distance from x and within
+    // the current view. If there is no such frame, return an empty
+    // vector.
+    
     sv_frame_t frame = v->getFrameForX(x);
+    
+    EventVector exact = m_model->getEventsStartingAt(frame);
+    if (!exact.empty()) return exact;
 
-    SparseTimeValueModel::PointList onPoints =
-        m_model->getPoints(frame);
+    // overspill == 1, so one event either side of the given span
+    EventVector neighbouring = m_model->getEventsWithin
+        (frame, m_model->getResolution(), 1);
 
-    if (!onPoints.empty()) {
-        return onPoints;
-    }
-
-    SparseTimeValueModel::PointList prevPoints =
-        m_model->getPreviousPoints(frame);
-    SparseTimeValueModel::PointList nextPoints =
-        m_model->getNextPoints(frame);
-
-    SparseTimeValueModel::PointList usePoints = prevPoints;
-
-    if (prevPoints.empty()) {
-        usePoints = nextPoints;
-    } else if (nextPoints.empty()) {
-        // stick with prevPoints
-    } else if (prevPoints.begin()->frame < v->getStartFrame() &&
-               !(nextPoints.begin()->frame > v->getEndFrame())) {
-        usePoints = nextPoints;
-    } else if (nextPoints.begin()->frame - frame <
-               frame - prevPoints.begin()->frame) {
-        usePoints = nextPoints;
-    }
-
-    if (!usePoints.empty()) {
-        double fuzz = v->scaleSize(2);
-        int px = v->getXForFrame(usePoints.begin()->frame);
-        if ((px > x && px - x > fuzz) ||
-            (px < x && x - px > fuzz + 3)) {
-            usePoints.clear();
+    double fuzz = v->scaleSize(2);
+    sv_frame_t suitable = 0;
+    bool have = false;
+    
+    for (Event e: neighbouring) {
+        sv_frame_t f = e.getFrame();
+        if (f < v->getStartFrame() || f > v->getEndFrame()) {
+            continue;
+        }
+        int px = v->getXForFrame(f);
+        if ((px > x && px - x > fuzz) || (px < x && x - px > fuzz + 3)) {
+            continue;
+        }
+        if (!have) {
+            suitable = f;
+            have = true;
+        } else if (llabs(frame - f) < llabs(suitable - f)) {
+            suitable = f;
         }
     }
 
-    return usePoints;
+    if (have) {
+        return m_model->getEventsStartingAt(suitable);
+    } else {
+        return {};
+    }
 }
 
 QString
 TimeValueLayer::getLabelPreceding(sv_frame_t frame) const
 {
-    if (!m_model) return "";
-    SparseTimeValueModel::PointList points = m_model->getPreviousPoints(frame);
-    for (SparseTimeValueModel::PointList::const_iterator i = points.begin();
-         i != points.end(); ++i) {
-        if (i->label != "") return i->label;
+    if (!m_model || !m_model->hasTextLabels()) return "";
+
+    Event e;
+    if (m_model->getNearestEventMatching
+        (frame,
+         [](Event e) { return e.hasLabel() && e.getLabel() != ""; },
+         EventSeries::Backward,
+         e)) {
+        return e.getLabel();
     }
+
     return "";
 }
 
@@ -595,7 +603,7 @@
 
     if (!m_model || !m_model->getSampleRate()) return "";
 
-    SparseTimeValueModel::PointList points = getLocalPoints(v, x);
+    EventVector points = getLocalPoints(v, x);
 
     if (points.empty()) {
         if (!m_model->isReady()) {
@@ -605,12 +613,12 @@
         }
     }
 
-    sv_frame_t useFrame = points.begin()->frame;
+    sv_frame_t useFrame = points.begin()->getFrame();
 
     RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate());
     
     QString valueText;
-    float value = points.begin()->value;
+    float value = points.begin()->getValue();
     QString unit = getScaleUnits();
 
     if (unit == "Hz") {
@@ -626,7 +634,7 @@
     
     QString text;
 
-    if (points.begin()->label == "") {
+    if (points.begin()->getLabel() == "") {
         text = QString(tr("Time:\t%1\nValue:\t%2\nNo label"))
             .arg(rt.toText(true).c_str())
             .arg(valueText);
@@ -634,16 +642,17 @@
         text = QString(tr("Time:\t%1\nValue:\t%2\nLabel:\t%4"))
             .arg(rt.toText(true).c_str())
             .arg(valueText)
-            .arg(points.begin()->label);
+            .arg(points.begin()->getLabel());
     }
 
     pos = QPoint(v->getXForFrame(useFrame),
-                 getYForValue(v, points.begin()->value));
+                 getYForValue(v, points.begin()->getValue()));
     return text;
 }
 
 bool
-TimeValueLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
+TimeValueLayer::snapToFeatureFrame(LayerGeometryProvider *v,
+                                   sv_frame_t &frame,
                                    int &resolution,
                                    SnapType snap) const
 {
@@ -651,71 +660,38 @@
         return Layer::snapToFeatureFrame(v, frame, resolution, snap);
     }
 
+    // SnapLeft / SnapRight: return frame of nearest feature in that
+    // direction no matter how far away
+    //
+    // SnapNeighbouring: return frame of feature that would be used in
+    // an editing operation, i.e. closest feature in either direction
+    // but only if it is "close enough"
+    
     resolution = m_model->getResolution();
-    SparseTimeValueModel::PointList points;
 
     if (snap == SnapNeighbouring) {
-        
-        points = getLocalPoints(v, v->getXForFrame(frame));
+        EventVector points = getLocalPoints(v, v->getXForFrame(frame));
         if (points.empty()) return false;
-        frame = points.begin()->frame;
+        frame = points.begin()->getFrame();
         return true;
-    }    
-
-    points = m_model->getPoints(frame, frame);
-    sv_frame_t snapped = frame;
-    bool found = false;
-
-    for (SparseTimeValueModel::PointList::const_iterator i = points.begin();
-         i != points.end(); ++i) {
-
-        if (snap == SnapRight) {
-
-            if (i->frame > frame) {
-                snapped = i->frame;
-                found = true;
-                break;
-            }
-
-        } else if (snap == SnapLeft) {
-
-            if (i->frame <= frame) {
-                snapped = i->frame;
-                found = true; // don't break, as the next may be better
-            } else {
-                break;
-            }
-
-        } else { // nearest
-
-            SparseTimeValueModel::PointList::const_iterator j = i;
-            ++j;
-
-            if (j == points.end()) {
-
-                snapped = i->frame;
-                found = true;
-                break;
-
-            } else if (j->frame >= frame) {
-
-                if (j->frame - frame < frame - i->frame) {
-                    snapped = j->frame;
-                } else {
-                    snapped = i->frame;
-                }
-                found = true;
-                break;
-            }
-        }
     }
 
-    frame = snapped;
-    return found;
+    Event e;
+    if (m_model->getNearestEventMatching
+        (frame,
+         [](Event) { return true; },
+         snap == SnapLeft ? EventSeries::Backward : EventSeries::Forward,
+         e)) {
+        frame = e.getFrame();
+        return true;
+    }
+
+    return false;
 }
 
 bool
-TimeValueLayer::snapToSimilarFeature(LayerGeometryProvider *v, sv_frame_t &frame,
+TimeValueLayer::snapToSimilarFeature(LayerGeometryProvider *v,
+                                     sv_frame_t &frame,
                                      int &resolution,
                                      SnapType snap) const
 {
@@ -723,76 +699,39 @@
         return Layer::snapToSimilarFeature(v, frame, resolution, snap);
     }
 
+    // snap is only permitted to be SnapLeft or SnapRight here.
+    
     resolution = m_model->getResolution();
 
-    const SparseTimeValueModel::PointList &points = m_model->getPoints();
-    SparseTimeValueModel::PointList close = m_model->getPoints(frame, frame);
+    Event ref;
+    Event e;
+    float matchvalue;
+    bool found;
 
-    SparseTimeValueModel::PointList::const_iterator i;
+    found = m_model->getNearestEventMatching
+        (frame, [](Event) { return true; }, EventSeries::Backward, ref);
 
-    sv_frame_t matchframe = frame;
-    double matchvalue = 0.0;
-
-    for (i = close.begin(); i != close.end(); ++i) {
-        if (i->frame > frame) break;
-        matchvalue = i->value;
-        matchframe = i->frame;
+    if (!found) {
+        return false;
     }
 
-    sv_frame_t snapped = frame;
-    bool found = false;
-    bool distant = false;
-    double epsilon = 0.0001;
+    matchvalue = ref.getValue();
+    
+    found = m_model->getNearestEventMatching
+        (frame,
+         [matchvalue](Event e) {
+             double epsilon = 0.0001;
+             return fabs(e.getValue() - matchvalue) < epsilon;
+         },
+         snap == SnapLeft ? EventSeries::Backward : EventSeries::Forward,
+         e);
 
-    i = close.begin();
-
-    // Scan through the close points first, then the more distant ones
-    // if no suitable close one is found. So the while-termination
-    // condition here can only happen once i has passed through the
-    // whole of the close container and then the whole of the separate
-    // points container. The two iterators are totally distinct, but
-    // have the same type so we cheekily use the same variable and a
-    // single loop for both.
-
-    while (i != points.end()) {
-
-        if (!distant) {
-            if (i == close.end()) {
-                // switch from the close container to the points container
-                i = points.begin();
-                distant = true;
-            }
-        }
-
-        if (snap == SnapRight) {
-
-            if (i->frame > matchframe &&
-                fabs(i->value - matchvalue) < epsilon) {
-                snapped = i->frame;
-                found = true;
-                break;
-            }
-
-        } else if (snap == SnapLeft) {
-
-            if (i->frame < matchframe) {
-                if (fabs(i->value - matchvalue) < epsilon) {
-                    snapped = i->frame;
-                    found = true; // don't break, as the next may be better
-                }
-            } else if (found || distant) {
-                break;
-            }
-
-        } else { 
-            // no other snap types supported
-        }
-
-        ++i;
+    if (!found) {
+        return false;
     }
 
-    frame = snapped;
-    return found;
+    frame = e.getFrame();
+    return true;
 }
 
 void
@@ -926,8 +865,7 @@
     sv_frame_t frame1 = v->getFrameForX(x1);
     if (m_derivative) --frame0;
 
-    SparseTimeValueModel::PointList points(m_model->getPoints
-                                           (frame0, frame1));
+    EventVector points(m_model->getEventsWithin(frame0, frame1 - frame0, 1));
     if (points.empty()) return;
 
     paint.setPen(getBaseQColor());
@@ -938,7 +876,7 @@
 
 #ifdef DEBUG_TIME_VALUE_LAYER
     cerr << "TimeValueLayer::paint: resolution is "
-              << m_model->getResolution() << " frames" << endl;
+         << m_model->getResolution() << " frames" << endl;
 #endif
 
     double min = m_model->getValueMinimum();
@@ -952,12 +890,13 @@
     sv_frame_t illuminateFrame = -1;
 
     if (v->shouldIlluminateLocalFeatures(this, localPos)) {
-        SparseTimeValueModel::PointList localPoints =
-            getLocalPoints(v, localPos.x());
+        EventVector localPoints = getLocalPoints(v, localPos.x());
 #ifdef DEBUG_TIME_VALUE_LAYER
         cerr << "TimeValueLayer: " << localPoints.size() << " local points" << endl;
 #endif
-        if (!localPoints.empty()) illuminateFrame = localPoints.begin()->frame;
+        if (!localPoints.empty()) {
+            illuminateFrame = localPoints.begin()->getFrame();
+        }
     }
 
     int w =
@@ -990,21 +929,21 @@
     
     sv_frame_t prevFrame = 0;
 
-    for (SparseTimeValueModel::PointList::const_iterator i = points.begin();
+    for (EventVector::const_iterator i = points.begin();
          i != points.end(); ++i) {
 
         if (m_derivative && i == points.begin()) continue;
 
-        const SparseTimeValueModel::Point &p(*i);
+        Event p(*i);
 
-        double value = p.value;
+        double value = p.getValue();
         if (m_derivative) {
-            SparseTimeValueModel::PointList::const_iterator j = i;
+            EventVector::const_iterator j = i;
             --j;
-            value -= j->value;
+            value -= j->getValue();
         }
 
-        int x = v->getXForFrame(p.frame);
+        int x = v->getXForFrame(p.getFrame());
         int y = getYForValue(v, value);
 
         bool gap = false;
@@ -1013,8 +952,8 @@
                 // Treat zeros as gaps
                 continue;
             }
-            gap = (p.frame > prevFrame &&
-                   (p.frame - prevFrame >= m_model->getResolution() * 2));
+            gap = (p.getFrame() > prevFrame &&
+                   (p.getFrame() - prevFrame >= m_model->getResolution() * 2));
         }
 
         if (m_plotStyle != PlotSegmentation) {
@@ -1031,20 +970,20 @@
         int nx = v->getXForFrame(nf);
         int ny = y;
 
-        SparseTimeValueModel::PointList::const_iterator j = i;
+        EventVector::const_iterator j = i;
         ++j;
 
         if (j != points.end()) {
-            const SparseTimeValueModel::Point &q(*j);
-            nvalue = q.value;
-            if (m_derivative) nvalue -= p.value;
-            nf = q.frame;
+            Event q(*j);
+            nvalue = q.getValue();
+            if (m_derivative) nvalue -= p.getValue();
+            nf = q.getFrame();
             nx = v->getXForFrame(nf);
             ny = getYForValue(v, nvalue);
             haveNext = true;
         }
 
-//        cout << "frame = " << p.frame << ", x = " << x << ", haveNext = " << haveNext 
+//        cout << "frame = " << p.getFrame() << ", x = " << x << ", haveNext = " << haveNext 
 //                  << ", nx = " << nx << endl;
 
         QPen pen(getBaseQColor());
@@ -1074,7 +1013,7 @@
 
         bool illuminate = false;
 
-        if (illuminateFrame == p.frame) {
+        if (illuminateFrame == p.getFrame()) {
 
             // not equipped to illuminate the right section in line
             // or curve mode
@@ -1138,7 +1077,7 @@
                     if (m_plotStyle == PlotDiscreteCurves) {
                         bool nextGap =
                             (nvalue == 0.0) ||
-                            (nf - p.frame >= m_model->getResolution() * 2);
+                            (nf - p.getFrame() >= m_model->getResolution() * 2);
                         if (nextGap) {
                             x1 = x0;
                             y1 = y0;
@@ -1188,7 +1127,7 @@
 
         if (v->shouldShowFeatureLabels()) {
 
-            QString label = p.label;
+            QString label = p.getLabel();
             bool italic = false;
 
             if (label == "" &&
@@ -1196,7 +1135,7 @@
                  m_plotStyle == PlotSegmentation ||
                  m_plotStyle == PlotConnectedPoints)) {
                 char lc[20];
-                snprintf(lc, 20, "%.3g", p.value);
+                snprintf(lc, 20, "%.3g", p.getValue());
                 label = lc;
                 italic = true;
             }
@@ -1209,15 +1148,16 @@
                 if (haveRoom ||
                     (!haveNext &&
                      (pointCount == 0 || !italic))) {
-                    PaintAssistant::drawVisibleText(v, paint, x + 5, textY, label,
-                                       italic ?
-                                       PaintAssistant::OutlinedItalicText :
-                                       PaintAssistant::OutlinedText);
+                    PaintAssistant::drawVisibleText
+                        (v, paint, x + 5, textY, label,
+                         italic ?
+                         PaintAssistant::OutlinedItalicText :
+                         PaintAssistant::OutlinedText);
                 }
             }
         }
 
-        prevFrame = p.frame;
+        prevFrame = p.getFrame();
         ++pointCount;
     }
 
@@ -1261,7 +1201,7 @@
 void
 TimeValueLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const
 {
-    if (!m_model || m_model->getPoints().empty()) return;
+    if (!m_model || m_model->isEmpty()) return;
 
     QString unit;
     double min, max;
@@ -1328,13 +1268,13 @@
 
     bool havePoint = false;
 
-    SparseTimeValueModel::PointList points = getLocalPoints(v, e->x());
+    EventVector points = getLocalPoints(v, e->x());
     if (!points.empty()) {
-        for (SparseTimeValueModel::PointList::iterator i = points.begin();
+        for (EventVector::iterator i = points.begin();
              i != points.end(); ++i) {
-            if (((i->frame / resolution) * resolution) != frame) {
+            if (((i->getFrame() / resolution) * resolution) != frame) {
 #ifdef DEBUG_TIME_VALUE_LAYER
-                cerr << "ignoring out-of-range frame at " << i->frame << endl;
+                cerr << "ignoring out-of-range frame at " << i->getFrame() << endl;
 #endif
                 continue;
             }
@@ -1344,17 +1284,15 @@
     }
 
     if (!havePoint) {
-        m_editingPoint = SparseTimeValueModel::Point
-            (frame, float(value), tr("New Point"));
+        m_editingPoint = Event(frame, float(value), tr("New Point"));
     }
 
     m_originalPoint = m_editingPoint;
 
     if (m_editingCommand) finish(m_editingCommand);
-    m_editingCommand = new SparseTimeValueModel::EditCommand(m_model,
-                                                             tr("Draw Point"));
+    m_editingCommand = new ChangeEventsCommand(m_model, tr("Draw Point"));
     if (!havePoint) {
-        m_editingCommand->addPoint(m_editingPoint);
+        m_editingCommand->add(m_editingPoint);
     }
 
     m_editing = true;
@@ -1376,7 +1314,7 @@
 
     double value = getValueForY(v, e->y());
 
-    SparseTimeValueModel::PointList points = getLocalPoints(v, e->x());
+    EventVector points = getLocalPoints(v, e->x());
 
 #ifdef DEBUG_TIME_VALUE_LAYER
     cerr << points.size() << " points" << endl;
@@ -1385,41 +1323,41 @@
     bool havePoint = false;
 
     if (!points.empty()) {
-        for (SparseTimeValueModel::PointList::iterator i = points.begin();
+        for (EventVector::iterator i = points.begin();
              i != points.end(); ++i) {
-            if (i->frame == m_editingPoint.frame &&
-                i->value == m_editingPoint.value) {
+            if (i->getFrame() == m_editingPoint.getFrame() &&
+                i->getValue() == m_editingPoint.getValue()) {
 #ifdef DEBUG_TIME_VALUE_LAYER
-                cerr << "ignoring current editing point at " << i->frame << ", " << i->value << endl;
+                cerr << "ignoring current editing point at " << i->getFrame() << ", " << i->getValue() << endl;
 #endif
                 continue;
             }
-            if (((i->frame / resolution) * resolution) != frame) {
+            if (((i->getFrame() / resolution) * resolution) != frame) {
 #ifdef DEBUG_TIME_VALUE_LAYER
-                cerr << "ignoring out-of-range frame at " << i->frame << endl;
+                cerr << "ignoring out-of-range frame at " << i->getFrame() << endl;
 #endif
                 continue;
             }
 #ifdef DEBUG_TIME_VALUE_LAYER
-            cerr << "adjusting to new point at " << i->frame << ", " << i->value << endl;
+            cerr << "adjusting to new point at " << i->getFrame() << ", " << i->getValue() << endl;
 #endif
             m_editingPoint = *i;
             m_originalPoint = m_editingPoint;
-            m_editingCommand->deletePoint(m_editingPoint);
+            m_editingCommand->remove(m_editingPoint);
             havePoint = true;
         }
     }
 
     if (!havePoint) {
-        if (frame == m_editingPoint.frame) {
-            m_editingCommand->deletePoint(m_editingPoint);
+        if (frame == m_editingPoint.getFrame()) {
+            m_editingCommand->remove(m_editingPoint);
         }
     }
 
-//    m_editingCommand->deletePoint(m_editingPoint);
-    m_editingPoint.frame = frame;
-    m_editingPoint.value = float(value);
-    m_editingCommand->addPoint(m_editingPoint);
+    m_editingPoint = m_editingPoint
+        .withFrame(frame)
+        .withValue(float(value));
+    m_editingCommand->add(m_editingPoint);
 }
 
 void
@@ -1439,7 +1377,7 @@
 {
     if (!m_model) return;
 
-    SparseTimeValueModel::PointList points = getLocalPoints(v, e->x());
+    EventVector points = getLocalPoints(v, e->x());
     if (points.empty()) return;
 
     m_editingPoint = *points.begin();
@@ -1464,16 +1402,13 @@
 
     m_editing = false;
 
-    SparseTimeValueModel::PointList points = getLocalPoints(v, e->x());
+    EventVector points = getLocalPoints(v, e->x());
     if (points.empty()) return;
-    if (points.begin()->frame != m_editingPoint.frame ||
-        points.begin()->value != m_editingPoint.value) return;
+    if (points.begin()->getFrame() != m_editingPoint.getFrame() ||
+        points.begin()->getValue() != m_editingPoint.getValue()) return;
 
-    m_editingCommand = new SparseTimeValueModel::EditCommand
-        (m_model, tr("Erase Point"));
-
-    m_editingCommand->deletePoint(m_editingPoint);
-
+    m_editingCommand = new ChangeEventsCommand(m_model, tr("Erase Point"));
+    m_editingCommand->remove(m_editingPoint);
     finish(m_editingCommand);
     m_editingCommand = nullptr;
     m_editing = false;
@@ -1488,7 +1423,7 @@
 
     if (!m_model) return;
 
-    SparseTimeValueModel::PointList points = getLocalPoints(v, e->x());
+    EventVector points = getLocalPoints(v, e->x());
     if (points.empty()) return;
 
     m_editingPoint = *points.begin();
@@ -1518,14 +1453,14 @@
     double value = getValueForY(v, e->y());
 
     if (!m_editingCommand) {
-        m_editingCommand = new SparseTimeValueModel::EditCommand(m_model,
-                                                                 tr("Drag Point"));
+        m_editingCommand = new ChangeEventsCommand(m_model, tr("Drag Point"));
     }
 
-    m_editingCommand->deletePoint(m_editingPoint);
-    m_editingPoint.frame = frame;
-    m_editingPoint.value = float(value);
-    m_editingCommand->addPoint(m_editingPoint);
+    m_editingCommand->remove(m_editingPoint);
+    m_editingPoint = m_editingPoint
+        .withFrame(frame)
+        .withValue(float(value));
+    m_editingCommand->add(m_editingPoint);
 }
 
 void
@@ -1540,8 +1475,8 @@
 
         QString newName = m_editingCommand->getName();
 
-        if (m_editingPoint.frame != m_originalPoint.frame) {
-            if (m_editingPoint.value != m_originalPoint.value) {
+        if (m_editingPoint.getFrame() != m_originalPoint.getFrame()) {
+            if (m_editingPoint.getValue() != m_originalPoint.getValue()) {
                 newName = tr("Edit Point");
             } else {
                 newName = tr("Relocate Point");
@@ -1563,10 +1498,10 @@
 {
     if (!m_model) return false;
 
-    SparseTimeValueModel::PointList points = getLocalPoints(v, e->x());
+    EventVector points = getLocalPoints(v, e->x());
     if (points.empty()) return false;
 
-    SparseTimeValueModel::Point point = *points.begin();
+    Event point = *points.begin();
 
     ItemEditDialog *dialog = new ItemEditDialog
         (m_model->getSampleRate(),
@@ -1575,21 +1510,21 @@
          ItemEditDialog::ShowText,
          getScaleUnits());
 
-    dialog->setFrameTime(point.frame);
-    dialog->setValue(point.value);
-    dialog->setText(point.label);
+    dialog->setFrameTime(point.getFrame());
+    dialog->setValue(point.getValue());
+    dialog->setText(point.getLabel());
 
     if (dialog->exec() == QDialog::Accepted) {
 
-        SparseTimeValueModel::Point newPoint = point;
-        newPoint.frame = dialog->getFrameTime();
-        newPoint.value = dialog->getValue();
-        newPoint.label = dialog->getText();
+        Event newPoint = point
+            .withFrame(dialog->getFrameTime())
+            .withValue(dialog->getValue())
+            .withLabel(dialog->getText());
         
-        SparseTimeValueModel::EditCommand *command =
-            new SparseTimeValueModel::EditCommand(m_model, tr("Edit Point"));
-        command->deletePoint(point);
-        command->addPoint(newPoint);
+        ChangeEventsCommand *command =
+            new ChangeEventsCommand(m_model, tr("Edit Point"));
+        command->remove(point);
+        command->add(newPoint);
         finish(command);
     }
 
@@ -1602,22 +1537,18 @@
 {
     if (!m_model) return;
 
-    SparseTimeValueModel::EditCommand *command =
-        new SparseTimeValueModel::EditCommand(m_model,
-                                              tr("Drag Selection"));
+    ChangeEventsCommand *command =
+        new ChangeEventsCommand(m_model, tr("Drag Selection"));
 
-    SparseTimeValueModel::PointList points =
-        m_model->getPoints(s.getStartFrame(), s.getEndFrame());
+    EventVector points =
+        m_model->getEventsWithin(s.getStartFrame(), s.getDuration());
 
-    for (SparseTimeValueModel::PointList::iterator i = points.begin();
-         i != points.end(); ++i) {
+    for (Event p: points) {
 
-        if (s.contains(i->frame)) {
-            SparseTimeValueModel::Point newPoint(*i);
-            newPoint.frame = i->frame + newStartFrame - s.getStartFrame();
-            command->deletePoint(*i);
-            command->addPoint(newPoint);
-        }
+        Event newPoint = p.withFrame
+            (p.getFrame() + newStartFrame - s.getStartFrame());
+        command->remove(p);
+        command->add(newPoint);
     }
 
     finish(command);
@@ -1626,33 +1557,26 @@
 void
 TimeValueLayer::resizeSelection(Selection s, Selection newSize)
 {
-    if (!m_model) return;
+    if (!m_model || !s.getDuration()) return;
 
-    SparseTimeValueModel::EditCommand *command =
-        new SparseTimeValueModel::EditCommand(m_model,
-                                              tr("Resize Selection"));
+    ChangeEventsCommand *command =
+        new ChangeEventsCommand(m_model, tr("Resize Selection"));
 
-    SparseTimeValueModel::PointList points =
-        m_model->getPoints(s.getStartFrame(), s.getEndFrame());
+    EventVector points =
+        m_model->getEventsWithin(s.getStartFrame(), s.getDuration());
 
-    double ratio =
-        double(newSize.getEndFrame() - newSize.getStartFrame()) /
-        double(s.getEndFrame() - s.getStartFrame());
+    double ratio = double(newSize.getDuration()) / double(s.getDuration());
+    double oldStart = double(s.getStartFrame());
+    double newStart = double(newSize.getStartFrame());
 
-    for (SparseTimeValueModel::PointList::iterator i = points.begin();
-         i != points.end(); ++i) {
+    for (Event p: points) {
+        
+        double newFrame = (double(p.getFrame()) - oldStart) * ratio + newStart;
 
-        if (s.contains(i->frame)) {
-
-            double target = double(i->frame);
-            target = double(newSize.getStartFrame()) +
-                target - double(s.getStartFrame()) * ratio;
-
-            SparseTimeValueModel::Point newPoint(*i);
-            newPoint.frame = lrint(target);
-            command->deletePoint(*i);
-            command->addPoint(newPoint);
-        }
+        Event newPoint = p
+            .withFrame(lrint(newFrame));
+        command->remove(p);
+        command->add(newPoint);
     }
 
     finish(command);
@@ -1663,19 +1587,14 @@
 {
     if (!m_model) return;
 
-    SparseTimeValueModel::EditCommand *command =
-        new SparseTimeValueModel::EditCommand(m_model,
-                                              tr("Delete Selected Points"));
+    ChangeEventsCommand *command =
+        new ChangeEventsCommand(m_model, tr("Delete Selected Points"));
 
-    SparseTimeValueModel::PointList points =
-        m_model->getPoints(s.getStartFrame(), s.getEndFrame());
+    EventVector points =
+        m_model->getEventsWithin(s.getStartFrame(), s.getDuration());
 
-    for (SparseTimeValueModel::PointList::iterator i = points.begin();
-         i != points.end(); ++i) {
-
-        if (s.contains(i->frame)) {
-            command->deletePoint(*i);
-        }
+    for (Event p: points) {
+        command->remove(p);
     }
 
     finish(command);
@@ -1686,16 +1605,11 @@
 {
     if (!m_model) return;
 
-    SparseTimeValueModel::PointList points =
-        m_model->getPoints(s.getStartFrame(), s.getEndFrame());
+    EventVector points =
+        m_model->getEventsWithin(s.getStartFrame(), s.getDuration());
 
-    for (SparseTimeValueModel::PointList::iterator i = points.begin();
-         i != points.end(); ++i) {
-        if (s.contains(i->frame)) {
-            Clipboard::Point point(i->frame, i->value, i->label);
-            point.setReferenceFrame(alignToReference(v, i->frame));
-            to.addPoint(point);
-        }
+    for (Event p: points) {
+        to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
     }
 }
 
@@ -1705,7 +1619,7 @@
 {
     if (!m_model) return false;
 
-    const Clipboard::PointList &points = from.getPoints();
+    EventVector points = from.getPoints();
 
     bool realign = false;
 
@@ -1726,8 +1640,8 @@
         }
     }
 
-    SparseTimeValueModel::EditCommand *command =
-        new SparseTimeValueModel::EditCommand(m_model, tr("Paste"));
+    ChangeEventsCommand *command =
+        new ChangeEventsCommand(m_model, tr("Paste"));
 
     enum ValueAvailability {
         UnknownAvailability,
@@ -1746,18 +1660,16 @@
 
         ValueAvailability availability = UnknownAvailability;
 
-        for (Clipboard::PointList::const_iterator i = points.begin();
+        for (EventVector::const_iterator i = points.begin();
              i != points.end(); ++i) {
         
-            if (!i->haveFrame()) continue;
-
             if (availability == UnknownAvailability) {
-                if (i->haveValue()) availability = AllValues;
+                if (i->hasValue()) availability = AllValues;
                 else availability = NoValues;
                 continue;
             }
 
-            if (i->haveValue()) {
+            if (i->hasValue()) {
                 if (availability == NoValues) {
                     availability = SomeValues;
                 }
@@ -1768,7 +1680,7 @@
             }
 
             if (!haveUsableLabels) {
-                if (i->haveLabel()) {
+                if (i->hasLabel()) {
                     if (i->getLabel().contains(QRegExp("[0-9]"))) {
                         haveUsableLabels = true;
                     }
@@ -1836,13 +1748,11 @@
         }
     }
 
-    SparseTimeValueModel::Point prevPoint(0);
+    Event prevPoint;
 
-    for (Clipboard::PointList::const_iterator i = points.begin();
+    for (EventVector::const_iterator i = points.begin();
          i != points.end(); ++i) {
         
-        if (!i->haveFrame()) continue;
-
         sv_frame_t frame = 0;
 
         if (!realign) {
@@ -1851,7 +1761,7 @@
 
         } else {
 
-            if (i->haveReferenceFrame()) {
+            if (i->hasReferenceFrame()) {
                 frame = i->getReferenceFrame();
                 frame = alignFromReference(v, frame);
             } else {
@@ -1859,45 +1769,46 @@
             }
         }
 
-        SparseTimeValueModel::Point newPoint(frame);
-  
-        if (i->haveLabel()) {
-            newPoint.label = i->getLabel();
-        } else if (i->haveValue()) {
-            newPoint.label = QString("%1").arg(i->getValue());
+        Event newPoint = *i;
+        if (!i->hasLabel() && i->hasValue()) {
+            newPoint = newPoint.withLabel(QString("%1").arg(i->getValue()));
         }
 
         bool usePrev = false;
-        SparseTimeValueModel::Point formerPrevPoint = prevPoint;
+        Event formerPrevPoint = prevPoint;
 
-        if (i->haveValue()) {
-            newPoint.value = i->getValue();
-        } else {
+        if (!i->hasValue()) {
 #ifdef DEBUG_TIME_VALUE_LAYER
-            cerr << "Setting value on point at " << newPoint.frame << " from labeller";
+            cerr << "Setting value on point at " << newPoint.getFrame() << " from labeller";
             if (i == points.begin()) {
                 cerr << ", no prev point" << endl;
             } else {
-                cerr << ", prev point is at " << prevPoint.frame << endl;
+                cerr << ", prev point is at " << prevPoint.getFrame() << endl;
             }
 #endif
-            labeller.setValue<SparseTimeValueModel::Point>
+
+            Labeller::Revaluing valuing = 
+                labeller.revalue
                 (newPoint, (i == points.begin()) ? nullptr : &prevPoint);
+            
 #ifdef DEBUG_TIME_VALUE_LAYER
-            cerr << "New point value = " << newPoint.value << endl;
+            cerr << "New point value = " << newPoint.getValue() << endl;
 #endif
-            if (labeller.actingOnPrevPoint() && i != points.begin()) {
+            if (valuing.first == Labeller::AppliesToPreviousEvent) {
                 usePrev = true;
+                prevPoint = valuing.second;
+            } else {
+                newPoint = valuing.second;
             }
         }
 
         if (usePrev) {
-            command->deletePoint(formerPrevPoint);
-            command->addPoint(prevPoint);
+            command->remove(formerPrevPoint);
+            command->add(prevPoint);
         }
 
         prevPoint = newPoint;
-        command->addPoint(newPoint);
+        command->add(newPoint);
     }
 
     finish(command);