changeset 1738:4abc0f08adf9 by-id

More on alignment models and paths
author Chris Cannam
date Wed, 26 Jun 2019 10:21:15 +0100 (2019-06-26)
parents 5d631f6129fe
children 565575463752
files base/ById.h base/XmlExportable.h data/model/AlignmentModel.cpp data/model/AlignmentModel.h data/model/Path.h data/model/PathModel.h files.pri
diffstat 7 files changed, 220 insertions(+), 305 deletions(-) [+]
line wrap: on
line diff
--- a/base/ById.h	Tue Jun 25 18:56:57 2019 +0100
+++ b/base/ById.h	Wed Jun 26 10:21:15 2019 +0100
@@ -26,6 +26,8 @@
 #include <QMutex>
 #include <QString>
 
+#include "XmlExportable.h"
+
 template <typename T>
 struct SvId {
     
@@ -139,6 +141,21 @@
     std::shared_ptr<Derived> getAs(Id id) const {
         return std::dynamic_pointer_cast<Derived>(get(id));
     }
+
+    /**
+     * If the Item type is an XmlExportable, return the export ID of
+     * the given item ID. The export ID is a simple int, and is only
+     * allocated when first requested, so objects that are never
+     * exported don't get one.
+     */
+    int getExportId(Id id) const {
+        auto exportable = getAs<XmlExportable>(id);
+        if (exportable) {
+            return exportable->getExportId();
+        } else {
+            return XmlExportable::NO_ID;
+        }
+    }
     
 private:
     mutable QMutex m_mutex;
@@ -167,6 +184,10 @@
         return std::dynamic_pointer_cast<Derived>(get(id));
     }
 
+    static int getExportId(Id id) {
+        return byId().getExportId(id);
+    }
+    
 private:
     static
     ById<Item, Id> &byId() {
--- a/base/XmlExportable.h	Tue Jun 25 18:56:57 2019 +0100
+++ b/base/XmlExportable.h	Wed Jun 26 10:21:15 2019 +0100
@@ -25,7 +25,12 @@
 class XmlExportable
 {
 public:
-    XmlExportable() : m_exportId(-1) { }
+    enum {
+        // The value NO_ID (-1) is never allocated as an export id
+        NO_ID = -1
+    };
+
+    XmlExportable() : m_exportId(NO_ID) { }
     virtual ~XmlExportable() { }
 
     /**
--- a/data/model/AlignmentModel.cpp	Tue Jun 25 18:56:57 2019 +0100
+++ b/data/model/AlignmentModel.cpp	Wed Jun 26 10:21:15 2019 +0100
@@ -45,15 +45,6 @@
 #ifdef DEBUG_ALIGNMENT_MODEL
     SVCERR << "AlignmentModel(" << this << ")::~AlignmentModel()" << endl;
 #endif
-
-//!!!    if (m_pathSource) m_pathSource->aboutToDelete();
-//    delete m_pathSource;
-
-//    if (m_path) m_path->aboutToDelete();
-//    delete m_path;
-
-//    if (m_reversePath) m_reversePath->aboutToDelete();
-//    delete m_reversePath;
 }
 
 bool
@@ -171,11 +162,16 @@
     cerr << "AlignmentModel::toReference(" << frame << ")" << endl;
 #endif
     if (!m_path) {
-        if (m_pathSource.isNone()) return frame;
+        if (m_pathSource.isNone()) {
+            return frame;
+        }
         constructPath();
-        if (!m_path) return frame;
     }
-    return align(*m_path, frame);
+    if (!m_path) {
+        return frame;
+    }
+
+    return performAlignment(*m_path, frame);
 }
 
 sv_frame_t
@@ -185,25 +181,16 @@
     cerr << "AlignmentModel::fromReference(" << frame << ")" << endl;
 #endif
     if (!m_reversePath) {
-        if (m_pathSource.isNone()) return frame;
+        if (m_pathSource.isNone()) {
+            return frame;
+        }
         constructReversePath();
-        if (!m_reversePath) return frame;
     }
-    return align(*m_reversePath, frame);
-}
+    if (!m_reversePath) {
+        return frame;
+    }
 
-void
-AlignmentModel::pathSourceChanged()
-{
-    if (m_pathComplete) {
-/*!!!
-        cerr << "AlignmentModel: deleting raw path model" << endl;
-        if (m_pathSource) m_pathSource->aboutToDelete();
-        delete m_pathSource;
-        m_pathSource = nullptr;
-*/
-        m_pathSource = {};
-    }
+    return performAlignment(*m_reversePath, frame);
 }
 
 void
@@ -263,9 +250,9 @@
                       << "No raw path available" << endl;
             return;
         }
-        m_path.reset(new PathModel
+        m_path.reset(new Path
                      (pathSourceModel->getSampleRate(),
-                      pathSourceModel->getResolution(), false));
+                      pathSourceModel->getResolution()));
     } else {
         if (!pathSourceModel) return;
     }
@@ -295,21 +282,20 @@
                       << "No forward path available" << endl;
             return;
         }
-        m_reversePath.reset(new PathModel
+        m_reversePath.reset(new Path
                             (m_path->getSampleRate(),
-                             m_path->getResolution(), false));
+                             m_path->getResolution()));
     } else {
         if (!m_path) return;
     }
         
     m_reversePath->clear();
 
-    PathModel::PointList points = m_path->getPoints();
+    Path::Points points = m_path->getPoints();
         
-    for (PathModel::PointList::const_iterator i = points.begin();
-         i != points.end(); ++i) {
-        sv_frame_t frame = i->frame;
-        sv_frame_t rframe = i->mapframe;
+    for (auto p: points) {
+        sv_frame_t frame = p.frame;
+        sv_frame_t rframe = p.mapframe;
         m_reversePath->add(PathPoint(rframe, frame));
     }
 
@@ -319,14 +305,14 @@
 }
 
 sv_frame_t
-AlignmentModel::align(const PathModel &path, sv_frame_t frame) const
+AlignmentModel::performAlignment(const Path &path, sv_frame_t frame) const
 {
     // The path consists of a series of points, each with frame equal
     // to the frame on the source model and mapframe equal to the
     // frame on the target model.  Both should be monotonically
     // increasing.
 
-    const PathModel::PointList &points = path.getPoints();
+    const Path::Points &points = path.getPoints();
 
     if (points.empty()) {
 #ifdef DEBUG_ALIGNMENT_MODEL
@@ -340,14 +326,16 @@
 #endif
 
     PathPoint point(frame);
-    PathModel::PointList::const_iterator i = points.lower_bound(point);
+    Path::Points::const_iterator i = points.lower_bound(point);
     if (i == points.end()) {
 #ifdef DEBUG_ALIGNMENT_MODEL
         cerr << "Note: i == points.end()" << endl;
 #endif
         --i;
     }
-    while (i != points.begin() && i->frame > frame) --i;
+    while (i != points.begin() && i->frame > frame) {
+        --i;
+    }
 
     sv_frame_t foundFrame = i->frame;
     sv_frame_t foundMapFrame = i->mapframe;
@@ -373,7 +361,9 @@
          << followingMapFrame << endl;
 #endif
     
-    if (foundMapFrame < 0) return 0;
+    if (foundMapFrame < 0) {
+        return 0;
+    }
 
     sv_frame_t resultFrame = foundMapFrame;
 
@@ -401,9 +391,6 @@
     
     if (pathSourceModel) {
 
-        connect(pathSourceModel.get(), SIGNAL(modelChanged()),
-                this, SLOT(pathSourceChanged()));
-
         connect(pathSourceModel.get(),
                 SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
                 this, SLOT(pathSourceChangedWithin(sv_frame_t, sv_frame_t)));
@@ -421,20 +408,11 @@
 }
 
 void
-AlignmentModel::setPath(const PathModel &path)
+AlignmentModel::setPath(const Path &path)
 {
-//!!!    if (m_path) m_path->aboutToDelete();
-//    delete m_path;
-    m_path = path;
+    m_path.reset(new Path(path));
     m_pathComplete = true;
-#ifdef DEBUG_ALIGNMENT_MODEL
-    cerr << "AlignmentModel::setPath: path = " << m_path << endl;
-#endif
     constructReversePath();
-#ifdef DEBUG_ALIGNMENT_MODEL
-    cerr << "AlignmentModel::setPath: after construction path = "
-              << m_path << ", rpath = " << m_reversePath << endl;
-#endif
 }
     
 void
@@ -451,10 +429,8 @@
 
     Model::toXml(stream, indent,
                  QString("type=\"alignment\" reference=\"%1\" aligned=\"%2\" path=\"%3\" %4")
-                 .arg(m_reference->getExportId())
-                 .arg(m_aligned->getExportId())
+                 .arg(ModelById::getExportId(m_reference))
+                 .arg(ModelById::getExportId(m_aligned))
                  .arg(m_path->getExportId())
                  .arg(extraAttributes));
 }
-
-
--- a/data/model/AlignmentModel.h	Tue Jun 25 18:56:57 2019 +0100
+++ b/data/model/AlignmentModel.h	Wed Jun 26 10:21:15 2019 +0100
@@ -17,7 +17,7 @@
 #define SV_ALIGNMENT_MODEL_H
 
 #include "Model.h"
-#include "PathModel.h"
+#include "Path.h"
 #include "base/RealTime.h"
 
 #include <QString>
@@ -60,7 +60,7 @@
     sv_frame_t fromReference(sv_frame_t frame) const;
 
     void setPathFrom(ModelId pathSource); // a SparseTimeValueModel
-    void setPath(const PathModel &path);
+    void setPath(const Path &path);
 
     void toXml(QTextStream &stream,
                        QString indent = "",
@@ -72,12 +72,9 @@
     }
 
 signals:
-    void modelChanged();
-    void modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame);
     void completionChanged();
 
 protected slots:
-    void pathSourceChanged();
     void pathSourceChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame);
     void pathSourceCompletionChanged();
 
@@ -87,8 +84,8 @@
 
     ModelId m_pathSource; // a SparseTimeValueModel
 
-    mutable std::unique_ptr<PathModel> m_path;
-    mutable std::unique_ptr<PathModel> m_reversePath;
+    mutable std::unique_ptr<Path> m_path;
+    mutable std::unique_ptr<Path> m_reversePath;
     bool m_pathBegun;
     bool m_pathComplete;
     QString m_error;
@@ -96,7 +93,7 @@
     void constructPath() const;
     void constructReversePath() const;
 
-    sv_frame_t align(const PathModel &path, sv_frame_t frame) const;
+    sv_frame_t performAlignment(const Path &path, sv_frame_t frame) const;
 };
 
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/Path.h	Wed Jun 26 10:21:15 2019 +0100
@@ -0,0 +1,151 @@
+/* -*- 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 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 SV_PATH_H
+#define SV_PATH_H
+
+#include "base/XmlExportable.h"
+#include "base/RealTime.h"
+#include "base/BaseTypes.h"
+
+#include <QStringList>
+#include <set>
+
+struct PathPoint
+{
+    PathPoint(sv_frame_t _frame) :
+        frame(_frame), mapframe(_frame) { }
+    PathPoint(sv_frame_t _frame, sv_frame_t _mapframe) :
+        frame(_frame), mapframe(_mapframe) { }
+
+    sv_frame_t frame;
+    sv_frame_t mapframe;
+
+    void toXml(QTextStream &stream, QString indent = "",
+               QString extraAttributes = "") const {
+        stream << QString("%1<point frame=\"%2\" mapframe=\"%3\" %4/>\n")
+            .arg(indent).arg(frame).arg(mapframe).arg(extraAttributes);
+    }
+        
+    QString toDelimitedDataString(QString delimiter, DataExportOptions,
+                                  sv_samplerate_t sampleRate) const {
+        QStringList list;
+        list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str();
+        list << QString("%1").arg(mapframe);
+        return list.join(delimiter);
+    }
+
+    bool operator<(const PathPoint &p2) const {
+        if (frame != p2.frame) return frame < p2.frame;
+        return mapframe < p2.mapframe;
+    }
+};
+
+class Path : public XmlExportable
+{
+public:
+    Path(sv_samplerate_t sampleRate, int resolution) :
+        m_sampleRate(sampleRate),
+        m_resolution(resolution) {
+    }
+    Path(const Path &) =default;
+    Path &operator=(const Path &) =default;
+
+    typedef std::set<PathPoint> Points;
+
+    sv_samplerate_t getSampleRate() const { return m_sampleRate; }
+    int getResolution() const { return m_resolution; }
+
+    int getPointCount() const {
+        return int(m_points.size());
+    }
+
+    const Points &getPoints() const {
+        return m_points;
+    }
+
+    void add(PathPoint p) {
+        m_points.insert(p);
+    }
+    
+    void remove(PathPoint p) {
+        m_points.erase(p);
+    }
+
+    void clear() {
+        m_points.clear();
+    }
+
+    /**
+     * XmlExportable methods.
+     */
+    void toXml(QTextStream &out,
+               QString indent = "",
+               QString extraAttributes = "") const override {
+
+        // For historical reasons we serialise a Path as a model,
+        // although the class itself no longer is.
+        
+        // Our dataset doesn't have its own export ID, we just use
+        // ours. Actually any model could do that, since datasets
+        // aren't in the same id-space as models (or paths) when
+        // re-read
+        
+        out << indent;
+        out << QString("<model id=\"%1\" name=\"\" sampleRate=\"%2\" "
+                       "type=\"sparse\" dimensions=\"2\" resolution=\"%3\" "
+                       "notifyOnAdd=\"true\" dataset=\"%4\" "
+                       "subtype=\"path\" %5/>\n")
+            .arg(getExportId())
+            .arg(m_sampleRate)
+            .arg(m_resolution)
+            .arg(getExportId())
+            .arg(extraAttributes);
+
+        out << indent << QString("<dataset id=\"%1\" dimensions=\"2\">\n")
+            .arg(getExportId());
+        
+        for (PathPoint p: m_points) {
+            p.toXml(out, indent + "  ", "");
+        }
+
+        out << indent << "</dataset>\n";
+    }
+
+    QString toDelimitedDataString(QString delimiter,
+                                  DataExportOptions,
+                                  sv_frame_t startFrame,
+                                  sv_frame_t duration) const {
+
+        QString s;
+        for (PathPoint p: m_points) {
+            if (p.frame < startFrame) continue;
+            if (p.frame >= startFrame + duration) break;
+            s += QString("%1%2%3\n")
+                .arg(p.frame)
+                .arg(delimiter)
+                .arg(p.mapframe);
+        }
+
+        return s;
+    }
+    
+protected:
+    sv_samplerate_t m_sampleRate;
+    int m_resolution;
+    Points m_points;
+};
+
+
+#endif
--- a/data/model/PathModel.h	Tue Jun 25 18:56:57 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,235 +0,0 @@
-/* -*- 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 2007 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 SV_PATH_MODEL_H
-#define SV_PATH_MODEL_H
-
-#include "Model.h"
-#include "DeferredNotifier.h"
-#include "base/RealTime.h"
-#include "base/BaseTypes.h"
-
-#include "base/XmlExportable.h"
-#include "base/RealTime.h"
-
-#include <QStringList>
-#include <set>
-
-struct PathPoint
-{
-    PathPoint(sv_frame_t _frame) :
-        frame(_frame), mapframe(_frame) { }
-    PathPoint(sv_frame_t _frame, sv_frame_t _mapframe) :
-        frame(_frame), mapframe(_mapframe) { }
-
-    sv_frame_t frame;
-    sv_frame_t mapframe;
-
-    void toXml(QTextStream &stream, QString indent = "",
-               QString extraAttributes = "") const {
-        stream << QString("%1<point frame=\"%2\" mapframe=\"%3\" %4/>\n")
-            .arg(indent).arg(frame).arg(mapframe).arg(extraAttributes);
-    }
-        
-    QString toDelimitedDataString(QString delimiter, DataExportOptions,
-                                  sv_samplerate_t sampleRate) const {
-        QStringList list;
-        list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str();
-        list << QString("%1").arg(mapframe);
-        return list.join(delimiter);
-    }
-
-    bool operator<(const PathPoint &p2) const {
-        if (frame != p2.frame) return frame < p2.frame;
-        return mapframe < p2.mapframe;
-    }
-};
-
-//!!! pretty sure there is no good reason for this to be a model any
-//!!! more - it used to use implementation inheritance from
-//!!! SparseModel but that's no longer a thing. Should just be a
-//!!! simple container now, to be passed around by value/reference
-//!!! rather than on heap
-
-class PathModel : public Model
-{
-public:
-    typedef std::set<PathPoint> PointList;
-
-    PathModel(sv_samplerate_t sampleRate,
-              int resolution,
-              bool notifyOnAdd = true) :
-        m_sampleRate(sampleRate),
-        m_resolution(resolution),
-        m_notifier(this,
-                   notifyOnAdd ?
-                   DeferredNotifier::NOTIFY_ALWAYS :
-                   DeferredNotifier::NOTIFY_DEFERRED),
-        m_completion(100),
-        m_start(0),
-        m_end(0) {
-    }
-
-    QString getTypeName() const override { return tr("Path"); }
-    bool isSparse() const override { return true; }
-    bool isOK() const override { return true; }
-
-    sv_frame_t getStartFrame() const override {
-        return m_start;
-    }
-    sv_frame_t getTrueEndFrame() const override {
-        return m_end;
-    }
-    
-    sv_samplerate_t getSampleRate() const override { return m_sampleRate; }
-    int getResolution() const { return m_resolution; }
-    
-    int getCompletion() const override { 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 getPointCount() const {
-        return int(m_points.size());
-    }
-    const PointList &getPoints() const {
-        return m_points;
-    }
-
-    /**
-     * Editing methods.
-     */
-    void add(PathPoint p) {
-
-        {   QMutexLocker locker(&m_mutex);
-            m_points.insert(p);
-
-            if (m_start == m_end) {
-                m_start = p.frame;
-                m_end = m_start + m_resolution;
-            } else {
-                if (p.frame < m_start) {
-                    m_start = p.frame;
-                }
-                if (p.frame + m_resolution > m_end) {
-                    m_end = p.frame + m_resolution;
-                }
-            }
-        }
-        
-        m_notifier.update(p.frame, m_resolution);
-    }
-    
-    void remove(PathPoint p) {
-        {   QMutexLocker locker(&m_mutex);
-            m_points.erase(p);
-        }
-
-        emit modelChangedWithin(p.frame, p.frame + m_resolution);
-    }
-
-    void clear() {
-        {   QMutexLocker locker(&m_mutex);
-            m_start = m_end = 0;
-            m_points.clear();
-        }
-    }
-
-    /**
-     * XmlExportable methods.
-     */
-    void toXml(QTextStream &out,
-                       QString indent = "",
-                       QString extraAttributes = "") const override {
-
-        // Our dataset doesn't have its own export ID, we just use
-        // ours. Actually any model could do that, since datasets
-        // aren't in the same id-space as models when re-read
-        
-        Model::toXml
-            (out,
-             indent,
-             QString("type=\"sparse\" dimensions=\"2\" resolution=\"%1\" "
-                     "notifyOnAdd=\"%2\" dataset=\"%3\" subtype=\"path\" %4")
-             .arg(m_resolution)
-             .arg("true") // always true after model reaches 100% -
-                          // subsequent points are always notified
-             .arg(getExportId())
-             .arg(extraAttributes));
-
-        out << indent << QString("<dataset id=\"%1\" dimensions=\"2\">\n")
-            .arg(getExportId());
-        
-        for (PathPoint p: m_points) {
-            p.toXml(out, indent + "  ", "");
-        }
-
-        out << indent << "</dataset>\n";
-    }
-
-    QString toDelimitedDataString(QString delimiter,
-                                  DataExportOptions,
-                                  sv_frame_t startFrame,
-                                  sv_frame_t duration) const override {
-
-        QString s;
-        for (PathPoint p: m_points) {
-            if (p.frame < startFrame) continue;
-            if (p.frame >= startFrame + duration) break;
-            s += QString("%1%2%3\n")
-                .arg(p.frame)
-                .arg(delimiter)
-                .arg(p.mapframe);
-        }
-
-        return s;
-    }
-    
-protected:
-    sv_samplerate_t m_sampleRate;
-    int m_resolution;
-
-    DeferredNotifier m_notifier;
-    int m_completion;
-
-    sv_frame_t m_start;
-    sv_frame_t m_end;
-    PointList m_points;
-
-    mutable QMutex m_mutex;  
-};
-
-
-#endif
--- a/files.pri	Tue Jun 25 18:56:57 2019 +0100
+++ b/files.pri	Wed Jun 26 10:21:15 2019 +0100
@@ -90,7 +90,7 @@
            data/model/Model.h \
            data/model/ModelDataTableModel.h \
            data/model/NoteModel.h \
-           data/model/PathModel.h \
+           data/model/Path.h \
            data/model/PowerOfSqrtTwoZoomConstraint.h \
            data/model/PowerOfTwoZoomConstraint.h \
            data/model/RangeSummarisableTimeValueModel.h \