# HG changeset patch # User Chris Cannam # Date 1561540875 -3600 # Node ID 4abc0f08adf90c75545536bb32bad46eb433ac0b # Parent 5d631f6129fe3436d3b7367f9f5a78e65b1ef989 More on alignment models and paths diff -r 5d631f6129fe -r 4abc0f08adf9 base/ById.h --- 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 #include +#include "XmlExportable.h" + template struct SvId { @@ -139,6 +141,21 @@ std::shared_ptr getAs(Id id) const { return std::dynamic_pointer_cast(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(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(get(id)); } + static int getExportId(Id id) { + return byId().getExportId(id); + } + private: static ById &byId() { diff -r 5d631f6129fe -r 4abc0f08adf9 base/XmlExportable.h --- 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() { } /** diff -r 5d631f6129fe -r 4abc0f08adf9 data/model/AlignmentModel.cpp --- 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)); } - - diff -r 5d631f6129fe -r 4abc0f08adf9 data/model/AlignmentModel.h --- 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 @@ -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 m_path; - mutable std::unique_ptr m_reversePath; + mutable std::unique_ptr m_path; + mutable std::unique_ptr 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 diff -r 5d631f6129fe -r 4abc0f08adf9 data/model/Path.h --- /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 +#include + +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\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 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("\n") + .arg(getExportId()) + .arg(m_sampleRate) + .arg(m_resolution) + .arg(getExportId()) + .arg(extraAttributes); + + out << indent << QString("\n") + .arg(getExportId()); + + for (PathPoint p: m_points) { + p.toXml(out, indent + " ", ""); + } + + out << indent << "\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 diff -r 5d631f6129fe -r 4abc0f08adf9 data/model/PathModel.h --- 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 -#include - -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\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 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("\n") - .arg(getExportId()); - - for (PathPoint p: m_points) { - p.toXml(out, indent + " ", ""); - } - - out << indent << "\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 diff -r 5d631f6129fe -r 4abc0f08adf9 files.pri --- 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 \