Mercurial > hg > svcore
view data/model/AlignmentModel.cpp @ 1738:4abc0f08adf9 by-id
More on alignment models and paths
author | Chris Cannam |
---|---|
date | Wed, 26 Jun 2019 10:21:15 +0100 |
parents | 5d631f6129fe |
children | 6d09d68165a4 |
line wrap: on
line source
/* -*- 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. */ #include "AlignmentModel.h" #include "SparseTimeValueModel.h" //#define DEBUG_ALIGNMENT_MODEL 1 AlignmentModel::AlignmentModel(ModelId reference, ModelId aligned, ModelId pathSource) : m_reference(reference), m_aligned(aligned), m_pathSource(pathSource), m_path(nullptr), m_reversePath(nullptr), m_pathBegun(false), m_pathComplete(false) { setPathFrom(pathSource); if (m_reference == m_aligned) { // Trivial alignment, e.g. of main model to itself, which we // record so that we can distinguish the reference model for // alignments from an unaligned model. No path required m_pathComplete = true; } } AlignmentModel::~AlignmentModel() { #ifdef DEBUG_ALIGNMENT_MODEL SVCERR << "AlignmentModel(" << this << ")::~AlignmentModel()" << endl; #endif } bool AlignmentModel::isOK() const { if (m_error != "") return false; if (m_pathSource.isNone()) return true; auto pathSourceModel = ModelById::getAs<SparseTimeValueModel>(m_pathSource); if (pathSourceModel) { return pathSourceModel->isOK(); } return true; } sv_frame_t AlignmentModel::getStartFrame() const { auto reference = ModelById::get(m_reference); auto aligned = ModelById::get(m_aligned); if (reference && aligned) { sv_frame_t a = reference->getStartFrame(); sv_frame_t b = aligned->getStartFrame(); return std::min(a, b); } else { return 0; } } sv_frame_t AlignmentModel::getTrueEndFrame() const { auto reference = ModelById::get(m_reference); auto aligned = ModelById::get(m_aligned); if (reference && aligned) { sv_frame_t a = reference->getEndFrame(); sv_frame_t b = aligned->getEndFrame(); return std::max(a, b); } else { return 0; } } sv_samplerate_t AlignmentModel::getSampleRate() const { auto reference = ModelById::get(m_reference); if (reference) { return reference->getSampleRate(); } else { return 0; } } bool AlignmentModel::isReady(int *completion) const { if (!m_pathBegun && !m_pathSource.isNone()) { if (completion) *completion = 0; #ifdef DEBUG_ALIGNMENT_MODEL SVCERR << "AlignmentModel::isReady: path not begun" << endl; #endif return false; } if (m_pathComplete) { if (completion) *completion = 100; #ifdef DEBUG_ALIGNMENT_MODEL SVCERR << "AlignmentModel::isReady: path complete" << endl; #endif return true; } if (m_pathSource.isNone()) { // lack of raw path could mean path is complete (in which case // m_pathComplete true above) or else no path source has been // set at all yet (this case) if (completion) *completion = 0; #ifdef DEBUG_ALIGNMENT_MODEL SVCERR << "AlignmentModel::isReady: no raw path" << endl; #endif return false; } auto pathSourceModel = ModelById::getAs<SparseTimeValueModel>(m_pathSource); if (pathSourceModel) { return pathSourceModel->isReady(completion); } else { return true; // there is no meaningful answer here } } const ZoomConstraint * AlignmentModel::getZoomConstraint() const { return nullptr; } ModelId AlignmentModel::getReferenceModel() const { return m_reference; } ModelId AlignmentModel::getAlignedModel() const { return m_aligned; } sv_frame_t AlignmentModel::toReference(sv_frame_t frame) const { #ifdef DEBUG_ALIGNMENT_MODEL cerr << "AlignmentModel::toReference(" << frame << ")" << endl; #endif if (!m_path) { if (m_pathSource.isNone()) { return frame; } constructPath(); } if (!m_path) { return frame; } return performAlignment(*m_path, frame); } sv_frame_t AlignmentModel::fromReference(sv_frame_t frame) const { #ifdef DEBUG_ALIGNMENT_MODEL cerr << "AlignmentModel::fromReference(" << frame << ")" << endl; #endif if (!m_reversePath) { if (m_pathSource.isNone()) { return frame; } constructReversePath(); } if (!m_reversePath) { return frame; } return performAlignment(*m_reversePath, frame); } void AlignmentModel::pathSourceChangedWithin(sv_frame_t, sv_frame_t) { if (!m_pathComplete) return; constructPath(); constructReversePath(); } void AlignmentModel::pathSourceCompletionChanged() { auto pathSourceModel = ModelById::getAs<SparseTimeValueModel>(m_pathSource); if (!pathSourceModel) return; m_pathBegun = true; if (!m_pathComplete) { int completion = 0; pathSourceModel->isReady(&completion); #ifdef DEBUG_ALIGNMENT_MODEL SVCERR << "AlignmentModel::pathCompletionChanged: completion = " << completion << endl; #endif m_pathComplete = (completion == 100); if (m_pathComplete) { constructPath(); constructReversePath(); #ifdef DEBUG_ALIGNMENT_MODEL SVCERR << "AlignmentModel: path complete" << endl; #endif } } emit completionChanged(); } void AlignmentModel::constructPath() const { auto alignedModel = ModelById::get(m_aligned); if (!alignedModel) return; auto pathSourceModel = ModelById::getAs<SparseTimeValueModel>(m_pathSource); if (!m_path) { if (pathSourceModel) { cerr << "ERROR: AlignmentModel::constructPath: " << "No raw path available" << endl; return; } m_path.reset(new Path (pathSourceModel->getSampleRate(), pathSourceModel->getResolution())); } else { if (!pathSourceModel) return; } m_path->clear(); EventVector points = pathSourceModel->getAllEvents(); for (const auto &p: points) { sv_frame_t frame = p.getFrame(); double value = p.getValue(); sv_frame_t rframe = lrint(value * alignedModel->getSampleRate()); m_path->add(PathPoint(frame, rframe)); } #ifdef DEBUG_ALIGNMENT_MODEL cerr << "AlignmentModel::constructPath: " << m_path->getPointCount() << " points, at least " << (2 * m_path->getPointCount() * (3 * sizeof(void *) + sizeof(int) + sizeof(PathPoint))) << " bytes" << endl; #endif } void AlignmentModel::constructReversePath() const { if (!m_reversePath) { if (!m_path) { cerr << "ERROR: AlignmentModel::constructReversePath: " << "No forward path available" << endl; return; } m_reversePath.reset(new Path (m_path->getSampleRate(), m_path->getResolution())); } else { if (!m_path) return; } m_reversePath->clear(); Path::Points points = m_path->getPoints(); for (auto p: points) { sv_frame_t frame = p.frame; sv_frame_t rframe = p.mapframe; m_reversePath->add(PathPoint(rframe, frame)); } #ifdef DEBUG_ALIGNMENT_MODEL cerr << "AlignmentModel::constructReversePath: " << m_reversePath->getPointCount() << " points, at least " << (2 * m_reversePath->getPointCount() * (3 * sizeof(void *) + sizeof(int) + sizeof(PathPoint))) << " bytes" << endl; #endif } sv_frame_t 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 Path::Points &points = path.getPoints(); if (points.empty()) { #ifdef DEBUG_ALIGNMENT_MODEL cerr << "AlignmentModel::align: No points" << endl; #endif return frame; } #ifdef DEBUG_ALIGNMENT_MODEL cerr << "AlignmentModel::align: frame " << frame << " requested" << endl; #endif PathPoint point(frame); 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; } sv_frame_t foundFrame = i->frame; sv_frame_t foundMapFrame = i->mapframe; sv_frame_t followingFrame = foundFrame; sv_frame_t followingMapFrame = foundMapFrame; if (++i != points.end()) { #ifdef DEBUG_ALIGNMENT_MODEL cerr << "another point available" << endl; #endif followingFrame = i->frame; followingMapFrame = i->mapframe; } else { #ifdef DEBUG_ALIGNMENT_MODEL cerr << "no other point available" << endl; #endif } #ifdef DEBUG_ALIGNMENT_MODEL cerr << "foundFrame = " << foundFrame << ", foundMapFrame = " << foundMapFrame << ", followingFrame = " << followingFrame << ", followingMapFrame = " << followingMapFrame << endl; #endif if (foundMapFrame < 0) { return 0; } sv_frame_t resultFrame = foundMapFrame; if (followingFrame != foundFrame && frame > foundFrame) { double interp = double(frame - foundFrame) / double(followingFrame - foundFrame); resultFrame += lrint(double(followingMapFrame - foundMapFrame) * interp); } #ifdef DEBUG_ALIGNMENT_MODEL cerr << "AlignmentModel::align: resultFrame = " << resultFrame << endl; #endif return resultFrame; } void AlignmentModel::setPathFrom(ModelId pathSource) { m_pathSource = pathSource; auto pathSourceModel = ModelById::getAs<SparseTimeValueModel>(m_pathSource); if (pathSourceModel) { connect(pathSourceModel.get(), SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)), this, SLOT(pathSourceChangedWithin(sv_frame_t, sv_frame_t))); connect(pathSourceModel.get(), SIGNAL(completionChanged()), this, SLOT(pathSourceCompletionChanged())); constructPath(); constructReversePath(); if (pathSourceModel->isReady()) { pathSourceCompletionChanged(); } } } void AlignmentModel::setPath(const Path &path) { m_path.reset(new Path(path)); m_pathComplete = true; constructReversePath(); } void AlignmentModel::toXml(QTextStream &stream, QString indent, QString extraAttributes) const { if (!m_path) { SVDEBUG << "AlignmentModel::toXml: no path" << endl; return; } m_path->toXml(stream, indent, ""); Model::toXml(stream, indent, QString("type=\"alignment\" reference=\"%1\" aligned=\"%2\" path=\"%3\" %4") .arg(ModelById::getExportId(m_reference)) .arg(ModelById::getExportId(m_aligned)) .arg(m_path->getExportId()) .arg(extraAttributes)); }