Chris@297: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@297: Chris@297: /* Chris@297: Sonic Visualiser Chris@297: An audio file viewer and annotation editor. Chris@297: Centre for Digital Music, Queen Mary, University of London. Chris@297: This file copyright 2007 QMUL. Chris@297: Chris@297: This program is free software; you can redistribute it and/or Chris@297: modify it under the terms of the GNU General Public License as Chris@297: published by the Free Software Foundation; either version 2 of the Chris@297: License, or (at your option) any later version. See the file Chris@297: COPYING included with this distribution for more information. Chris@297: */ Chris@297: Chris@297: #include "AlignmentModel.h" Chris@297: Chris@297: #include "SparseTimeValueModel.h" Chris@297: Chris@409: //#define DEBUG_ALIGNMENT_MODEL 1 Chris@376: Chris@1735: AlignmentModel::AlignmentModel(ModelId reference, Chris@1735: ModelId aligned, Chris@1735: ModelId pathSource) : Chris@297: m_reference(reference), Chris@297: m_aligned(aligned), Chris@1735: m_pathSource(pathSource), Chris@1582: m_path(nullptr), Chris@1582: m_reversePath(nullptr), Chris@323: m_pathBegun(false), Chris@297: m_pathComplete(false) Chris@297: { Chris@1737: setPathFrom(pathSource); Chris@1696: Chris@1696: if (m_reference == m_aligned) { Chris@1696: // Trivial alignment, e.g. of main model to itself, which we Chris@1696: // record so that we can distinguish the reference model for Chris@1696: // alignments from an unaligned model. No path required Chris@1696: m_pathComplete = true; Chris@1696: } Chris@297: } Chris@297: Chris@297: AlignmentModel::~AlignmentModel() Chris@297: { Chris@1691: #ifdef DEBUG_ALIGNMENT_MODEL Chris@1691: SVCERR << "AlignmentModel(" << this << ")::~AlignmentModel()" << endl; Chris@1691: #endif Chris@297: } Chris@297: Chris@297: bool Chris@297: AlignmentModel::isOK() const Chris@297: { Chris@1705: if (m_error != "") return false; Chris@1737: if (m_pathSource.isNone()) return true; Chris@1737: auto pathSourceModel = Chris@1737: ModelById::getAs(m_pathSource); Chris@1737: if (pathSourceModel) { Chris@1737: return pathSourceModel->isOK(); Chris@1737: } Chris@1705: return true; Chris@297: } Chris@297: Chris@1038: sv_frame_t Chris@297: AlignmentModel::getStartFrame() const Chris@297: { Chris@1737: auto reference = ModelById::get(m_reference); Chris@1737: auto aligned = ModelById::get(m_aligned); Chris@1737: Chris@1737: if (reference && aligned) { Chris@1737: sv_frame_t a = reference->getStartFrame(); Chris@1737: sv_frame_t b = aligned->getStartFrame(); Chris@1737: return std::min(a, b); Chris@1737: } else { Chris@1737: return 0; Chris@1737: } Chris@297: } Chris@297: Chris@1038: sv_frame_t Chris@1725: AlignmentModel::getTrueEndFrame() const Chris@297: { Chris@1737: auto reference = ModelById::get(m_reference); Chris@1737: auto aligned = ModelById::get(m_aligned); Chris@1737: Chris@1737: if (reference && aligned) { Chris@1737: sv_frame_t a = reference->getEndFrame(); Chris@1737: sv_frame_t b = aligned->getEndFrame(); Chris@1737: return std::max(a, b); Chris@1737: } else { Chris@1737: return 0; Chris@1737: } Chris@297: } Chris@297: Chris@1040: sv_samplerate_t Chris@297: AlignmentModel::getSampleRate() const Chris@297: { Chris@1737: auto reference = ModelById::get(m_reference); Chris@1737: if (reference) { Chris@1737: return reference->getSampleRate(); Chris@1737: } else { Chris@1737: return 0; Chris@1737: } Chris@297: } Chris@297: Chris@297: bool Chris@297: AlignmentModel::isReady(int *completion) const Chris@297: { Chris@1737: if (!m_pathBegun && !m_pathSource.isNone()) { Chris@338: if (completion) *completion = 0; Chris@1561: #ifdef DEBUG_ALIGNMENT_MODEL Chris@1696: SVCERR << "AlignmentModel::isReady: path not begun" << endl; Chris@1561: #endif Chris@323: return false; Chris@323: } Chris@1016: if (m_pathComplete) { Chris@338: if (completion) *completion = 100; Chris@1561: #ifdef DEBUG_ALIGNMENT_MODEL Chris@1696: SVCERR << "AlignmentModel::isReady: path complete" << endl; Chris@1561: #endif Chris@338: return true; Chris@338: } Chris@1737: if (m_pathSource.isNone()) { Chris@1016: // lack of raw path could mean path is complete (in which case Chris@1737: // m_pathComplete true above) or else no path source has been Chris@1016: // set at all yet (this case) Chris@1016: if (completion) *completion = 0; Chris@1561: #ifdef DEBUG_ALIGNMENT_MODEL Chris@1696: SVCERR << "AlignmentModel::isReady: no raw path" << endl; Chris@1561: #endif Chris@1016: return false; Chris@1016: } Chris@1737: auto pathSourceModel = Chris@1737: ModelById::getAs(m_pathSource); Chris@1737: if (pathSourceModel) { Chris@1737: return pathSourceModel->isReady(completion); Chris@1737: } else { Chris@1737: return true; // there is no meaningful answer here Chris@1737: } Chris@297: } Chris@297: Chris@297: const ZoomConstraint * Chris@297: AlignmentModel::getZoomConstraint() const Chris@297: { Chris@1582: return nullptr; Chris@297: } Chris@297: Chris@1737: ModelId Chris@297: AlignmentModel::getReferenceModel() const Chris@297: { Chris@297: return m_reference; Chris@297: } Chris@297: Chris@1737: ModelId Chris@297: AlignmentModel::getAlignedModel() const Chris@297: { Chris@297: return m_aligned; Chris@297: } Chris@297: Chris@1038: sv_frame_t Chris@1038: AlignmentModel::toReference(sv_frame_t frame) const Chris@297: { Chris@376: #ifdef DEBUG_ALIGNMENT_MODEL Chris@1075: cerr << "AlignmentModel::toReference(" << frame << ")" << endl; Chris@376: #endif Chris@371: if (!m_path) { Chris@1738: if (m_pathSource.isNone()) { Chris@1738: return frame; Chris@1738: } Chris@371: constructPath(); Chris@371: } Chris@1738: if (!m_path) { Chris@1738: return frame; Chris@1738: } Chris@1738: Chris@1738: return performAlignment(*m_path, frame); Chris@297: } Chris@297: Chris@1038: sv_frame_t Chris@1038: AlignmentModel::fromReference(sv_frame_t frame) const Chris@297: { Chris@376: #ifdef DEBUG_ALIGNMENT_MODEL Chris@1075: cerr << "AlignmentModel::fromReference(" << frame << ")" << endl; Chris@376: #endif Chris@371: if (!m_reversePath) { Chris@1738: if (m_pathSource.isNone()) { Chris@1738: return frame; Chris@1738: } Chris@371: constructReversePath(); Chris@371: } Chris@1738: if (!m_reversePath) { Chris@1738: return frame; Chris@1738: } Chris@297: Chris@1738: return performAlignment(*m_reversePath, frame); Chris@297: } Chris@297: Chris@297: void Chris@1752: AlignmentModel::pathSourceChangedWithin(ModelId, sv_frame_t, sv_frame_t) Chris@297: { Chris@297: if (!m_pathComplete) return; Chris@338: constructPath(); Chris@297: constructReversePath(); Chris@297: } Chris@297: Chris@297: void Chris@1752: AlignmentModel::pathSourceCompletionChanged(ModelId) Chris@297: { Chris@1737: auto pathSourceModel = Chris@1737: ModelById::getAs(m_pathSource); Chris@1737: if (!pathSourceModel) return; Chris@1737: Chris@323: m_pathBegun = true; Chris@323: Chris@297: if (!m_pathComplete) { Chris@338: Chris@297: int completion = 0; Chris@1737: pathSourceModel->isReady(&completion); Chris@338: Chris@376: #ifdef DEBUG_ALIGNMENT_MODEL Chris@1666: SVCERR << "AlignmentModel::pathCompletionChanged: completion = " Chris@1666: << completion << endl; Chris@376: #endif Chris@338: Chris@323: m_pathComplete = (completion == 100); Chris@338: Chris@297: if (m_pathComplete) { Chris@338: Chris@338: constructPath(); Chris@297: constructReversePath(); Chris@1666: Chris@1691: #ifdef DEBUG_ALIGNMENT_MODEL Chris@1691: SVCERR << "AlignmentModel: path complete" << endl; Chris@1691: #endif Chris@297: } Chris@297: } Chris@323: Chris@1752: emit completionChanged(getId()); Chris@297: } Chris@297: Chris@297: void Chris@338: AlignmentModel::constructPath() const Chris@297: { Chris@1737: auto alignedModel = ModelById::get(m_aligned); Chris@1737: if (!alignedModel) return; Chris@1737: Chris@1737: auto pathSourceModel = Chris@1737: ModelById::getAs(m_pathSource); Chris@338: if (!m_path) { Chris@1737: if (pathSourceModel) { Chris@843: cerr << "ERROR: AlignmentModel::constructPath: " Chris@843: << "No raw path available" << endl; Chris@338: return; Chris@338: } Chris@1738: m_path.reset(new Path Chris@1737: (pathSourceModel->getSampleRate(), Chris@1738: pathSourceModel->getResolution())); Chris@338: } else { Chris@1737: if (!pathSourceModel) return; Chris@297: } Chris@297: Chris@338: m_path->clear(); Chris@297: Chris@1737: EventVector points = pathSourceModel->getAllEvents(); Chris@1651: Chris@1651: for (const auto &p: points) { Chris@1651: sv_frame_t frame = p.getFrame(); Chris@1651: double value = p.getValue(); Chris@1737: sv_frame_t rframe = lrint(value * alignedModel->getSampleRate()); Chris@1662: m_path->add(PathPoint(frame, rframe)); Chris@297: } Chris@297: Chris@376: #ifdef DEBUG_ALIGNMENT_MODEL Chris@1075: cerr << "AlignmentModel::constructPath: " << m_path->getPointCount() << " points, at least " << (2 * m_path->getPointCount() * (3 * sizeof(void *) + sizeof(int) + sizeof(PathPoint))) << " bytes" << endl; Chris@376: #endif Chris@338: } Chris@338: Chris@338: void Chris@338: AlignmentModel::constructReversePath() const Chris@338: { Chris@338: if (!m_reversePath) { Chris@407: if (!m_path) { Chris@843: cerr << "ERROR: AlignmentModel::constructReversePath: " Chris@843: << "No forward path available" << endl; Chris@407: return; Chris@407: } Chris@1738: m_reversePath.reset(new Path Chris@1737: (m_path->getSampleRate(), Chris@1738: m_path->getResolution())); Chris@338: } else { Chris@407: if (!m_path) return; Chris@338: } Chris@338: Chris@338: m_reversePath->clear(); Chris@407: Chris@1738: Path::Points points = m_path->getPoints(); Chris@407: Chris@1738: for (auto p: points) { Chris@1738: sv_frame_t frame = p.frame; Chris@1738: sv_frame_t rframe = p.mapframe; Chris@1662: m_reversePath->add(PathPoint(rframe, frame)); Chris@407: } Chris@338: Chris@376: #ifdef DEBUG_ALIGNMENT_MODEL Chris@1075: cerr << "AlignmentModel::constructReversePath: " << m_reversePath->getPointCount() << " points, at least " << (2 * m_reversePath->getPointCount() * (3 * sizeof(void *) + sizeof(int) + sizeof(PathPoint))) << " bytes" << endl; Chris@376: #endif Chris@297: } Chris@297: Chris@1038: sv_frame_t Chris@1738: AlignmentModel::performAlignment(const Path &path, sv_frame_t frame) const Chris@297: { Chris@338: // The path consists of a series of points, each with frame equal Chris@338: // to the frame on the source model and mapframe equal to the Chris@338: // frame on the target model. Both should be monotonically Chris@338: // increasing. Chris@297: Chris@1738: const Path::Points &points = path.getPoints(); Chris@297: Chris@297: if (points.empty()) { Chris@379: #ifdef DEBUG_ALIGNMENT_MODEL Chris@1075: cerr << "AlignmentModel::align: No points" << endl; Chris@379: #endif Chris@297: return frame; Chris@297: } Chris@297: Chris@376: #ifdef DEBUG_ALIGNMENT_MODEL Chris@1075: cerr << "AlignmentModel::align: frame " << frame << " requested" << endl; Chris@376: #endif Chris@376: Chris@1662: PathPoint point(frame); Chris@1738: Path::Points::const_iterator i = points.lower_bound(point); Chris@376: if (i == points.end()) { Chris@376: #ifdef DEBUG_ALIGNMENT_MODEL Chris@843: cerr << "Note: i == points.end()" << endl; Chris@376: #endif Chris@376: --i; Chris@376: } Chris@1738: while (i != points.begin() && i->frame > frame) { Chris@1738: --i; Chris@1738: } Chris@297: Chris@1038: sv_frame_t foundFrame = i->frame; Chris@1038: sv_frame_t foundMapFrame = i->mapframe; Chris@297: Chris@1038: sv_frame_t followingFrame = foundFrame; Chris@1038: sv_frame_t followingMapFrame = foundMapFrame; Chris@297: Chris@312: if (++i != points.end()) { Chris@376: #ifdef DEBUG_ALIGNMENT_MODEL Chris@843: cerr << "another point available" << endl; Chris@376: #endif Chris@312: followingFrame = i->frame; Chris@338: followingMapFrame = i->mapframe; Chris@376: } else { Chris@376: #ifdef DEBUG_ALIGNMENT_MODEL Chris@843: cerr << "no other point available" << endl; Chris@376: #endif Chris@376: } Chris@312: Chris@1075: #ifdef DEBUG_ALIGNMENT_MODEL Chris@1075: cerr << "foundFrame = " << foundFrame << ", foundMapFrame = " << foundMapFrame Chris@1075: << ", followingFrame = " << followingFrame << ", followingMapFrame = " Chris@1075: << followingMapFrame << endl; Chris@1075: #endif Chris@1075: Chris@1738: if (foundMapFrame < 0) { Chris@1738: return 0; Chris@1738: } Chris@312: Chris@1038: sv_frame_t resultFrame = foundMapFrame; Chris@312: Chris@1038: if (followingFrame != foundFrame && frame > foundFrame) { Chris@1038: double interp = Chris@1038: double(frame - foundFrame) / Chris@1038: double(followingFrame - foundFrame); Chris@1038: resultFrame += lrint(double(followingMapFrame - foundMapFrame) * interp); Chris@312: } Chris@312: Chris@376: #ifdef DEBUG_ALIGNMENT_MODEL Chris@1075: cerr << "AlignmentModel::align: resultFrame = " << resultFrame << endl; Chris@376: #endif Chris@312: Chris@312: return resultFrame; Chris@297: } Chris@407: Chris@407: void Chris@1737: AlignmentModel::setPathFrom(ModelId pathSource) Chris@1016: { Chris@1737: m_pathSource = pathSource; Chris@1737: Chris@1737: auto pathSourceModel = Chris@1737: ModelById::getAs(m_pathSource); Chris@1737: Chris@1737: if (pathSourceModel) { Chris@1016: Chris@1737: connect(pathSourceModel.get(), Chris@1752: SIGNAL(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t)), Chris@1752: this, SLOT(pathSourceChangedWithin(ModelId, sv_frame_t, sv_frame_t))); Chris@1737: Chris@1752: connect(pathSourceModel.get(), SIGNAL(completionChanged(ModelId)), Chris@1752: this, SLOT(pathSourceCompletionChanged(ModelId))); Chris@1737: Chris@1737: constructPath(); Chris@1737: constructReversePath(); Chris@1737: Chris@1737: if (pathSourceModel->isReady()) { Chris@1752: pathSourceCompletionChanged(m_pathSource); Chris@1737: } Chris@1712: } Chris@1016: } Chris@1016: Chris@1016: void Chris@1738: AlignmentModel::setPath(const Path &path) Chris@407: { Chris@1738: m_path.reset(new Path(path)); Chris@1560: m_pathComplete = true; Chris@407: constructReversePath(); Chris@407: } Chris@297: Chris@407: void Chris@407: AlignmentModel::toXml(QTextStream &stream, Chris@407: QString indent, Chris@407: QString extraAttributes) const Chris@407: { Chris@407: if (!m_path) { Chris@690: SVDEBUG << "AlignmentModel::toXml: no path" << endl; Chris@407: return; Chris@407: } Chris@407: Chris@407: m_path->toXml(stream, indent, ""); Chris@407: Chris@407: Model::toXml(stream, indent, Chris@407: QString("type=\"alignment\" reference=\"%1\" aligned=\"%2\" path=\"%3\" %4") Chris@1738: .arg(ModelById::getExportId(m_reference)) Chris@1738: .arg(ModelById::getExportId(m_aligned)) Chris@1677: .arg(m_path->getExportId()) Chris@407: .arg(extraAttributes)); Chris@407: }