annotate 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
rev   line source
Chris@297 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@297 2
Chris@297 3 /*
Chris@297 4 Sonic Visualiser
Chris@297 5 An audio file viewer and annotation editor.
Chris@297 6 Centre for Digital Music, Queen Mary, University of London.
Chris@297 7 This file copyright 2007 QMUL.
Chris@297 8
Chris@297 9 This program is free software; you can redistribute it and/or
Chris@297 10 modify it under the terms of the GNU General Public License as
Chris@297 11 published by the Free Software Foundation; either version 2 of the
Chris@297 12 License, or (at your option) any later version. See the file
Chris@297 13 COPYING included with this distribution for more information.
Chris@297 14 */
Chris@297 15
Chris@297 16 #include "AlignmentModel.h"
Chris@297 17
Chris@297 18 #include "SparseTimeValueModel.h"
Chris@297 19
Chris@409 20 //#define DEBUG_ALIGNMENT_MODEL 1
Chris@376 21
Chris@1735 22 AlignmentModel::AlignmentModel(ModelId reference,
Chris@1735 23 ModelId aligned,
Chris@1735 24 ModelId pathSource) :
Chris@297 25 m_reference(reference),
Chris@297 26 m_aligned(aligned),
Chris@1735 27 m_pathSource(pathSource),
Chris@1582 28 m_path(nullptr),
Chris@1582 29 m_reversePath(nullptr),
Chris@323 30 m_pathBegun(false),
Chris@297 31 m_pathComplete(false)
Chris@297 32 {
Chris@1737 33 setPathFrom(pathSource);
Chris@1696 34
Chris@1696 35 if (m_reference == m_aligned) {
Chris@1696 36 // Trivial alignment, e.g. of main model to itself, which we
Chris@1696 37 // record so that we can distinguish the reference model for
Chris@1696 38 // alignments from an unaligned model. No path required
Chris@1696 39 m_pathComplete = true;
Chris@1696 40 }
Chris@297 41 }
Chris@297 42
Chris@297 43 AlignmentModel::~AlignmentModel()
Chris@297 44 {
Chris@1691 45 #ifdef DEBUG_ALIGNMENT_MODEL
Chris@1691 46 SVCERR << "AlignmentModel(" << this << ")::~AlignmentModel()" << endl;
Chris@1691 47 #endif
Chris@297 48 }
Chris@297 49
Chris@297 50 bool
Chris@297 51 AlignmentModel::isOK() const
Chris@297 52 {
Chris@1705 53 if (m_error != "") return false;
Chris@1737 54 if (m_pathSource.isNone()) return true;
Chris@1737 55 auto pathSourceModel =
Chris@1737 56 ModelById::getAs<SparseTimeValueModel>(m_pathSource);
Chris@1737 57 if (pathSourceModel) {
Chris@1737 58 return pathSourceModel->isOK();
Chris@1737 59 }
Chris@1705 60 return true;
Chris@297 61 }
Chris@297 62
Chris@1038 63 sv_frame_t
Chris@297 64 AlignmentModel::getStartFrame() const
Chris@297 65 {
Chris@1737 66 auto reference = ModelById::get(m_reference);
Chris@1737 67 auto aligned = ModelById::get(m_aligned);
Chris@1737 68
Chris@1737 69 if (reference && aligned) {
Chris@1737 70 sv_frame_t a = reference->getStartFrame();
Chris@1737 71 sv_frame_t b = aligned->getStartFrame();
Chris@1737 72 return std::min(a, b);
Chris@1737 73 } else {
Chris@1737 74 return 0;
Chris@1737 75 }
Chris@297 76 }
Chris@297 77
Chris@1038 78 sv_frame_t
Chris@1725 79 AlignmentModel::getTrueEndFrame() const
Chris@297 80 {
Chris@1737 81 auto reference = ModelById::get(m_reference);
Chris@1737 82 auto aligned = ModelById::get(m_aligned);
Chris@1737 83
Chris@1737 84 if (reference && aligned) {
Chris@1737 85 sv_frame_t a = reference->getEndFrame();
Chris@1737 86 sv_frame_t b = aligned->getEndFrame();
Chris@1737 87 return std::max(a, b);
Chris@1737 88 } else {
Chris@1737 89 return 0;
Chris@1737 90 }
Chris@297 91 }
Chris@297 92
Chris@1040 93 sv_samplerate_t
Chris@297 94 AlignmentModel::getSampleRate() const
Chris@297 95 {
Chris@1737 96 auto reference = ModelById::get(m_reference);
Chris@1737 97 if (reference) {
Chris@1737 98 return reference->getSampleRate();
Chris@1737 99 } else {
Chris@1737 100 return 0;
Chris@1737 101 }
Chris@297 102 }
Chris@297 103
Chris@297 104 bool
Chris@297 105 AlignmentModel::isReady(int *completion) const
Chris@297 106 {
Chris@1737 107 if (!m_pathBegun && !m_pathSource.isNone()) {
Chris@338 108 if (completion) *completion = 0;
Chris@1561 109 #ifdef DEBUG_ALIGNMENT_MODEL
Chris@1696 110 SVCERR << "AlignmentModel::isReady: path not begun" << endl;
Chris@1561 111 #endif
Chris@323 112 return false;
Chris@323 113 }
Chris@1016 114 if (m_pathComplete) {
Chris@338 115 if (completion) *completion = 100;
Chris@1561 116 #ifdef DEBUG_ALIGNMENT_MODEL
Chris@1696 117 SVCERR << "AlignmentModel::isReady: path complete" << endl;
Chris@1561 118 #endif
Chris@338 119 return true;
Chris@338 120 }
Chris@1737 121 if (m_pathSource.isNone()) {
Chris@1016 122 // lack of raw path could mean path is complete (in which case
Chris@1737 123 // m_pathComplete true above) or else no path source has been
Chris@1016 124 // set at all yet (this case)
Chris@1016 125 if (completion) *completion = 0;
Chris@1561 126 #ifdef DEBUG_ALIGNMENT_MODEL
Chris@1696 127 SVCERR << "AlignmentModel::isReady: no raw path" << endl;
Chris@1561 128 #endif
Chris@1016 129 return false;
Chris@1016 130 }
Chris@1737 131 auto pathSourceModel =
Chris@1737 132 ModelById::getAs<SparseTimeValueModel>(m_pathSource);
Chris@1737 133 if (pathSourceModel) {
Chris@1737 134 return pathSourceModel->isReady(completion);
Chris@1737 135 } else {
Chris@1737 136 return true; // there is no meaningful answer here
Chris@1737 137 }
Chris@297 138 }
Chris@297 139
Chris@297 140 const ZoomConstraint *
Chris@297 141 AlignmentModel::getZoomConstraint() const
Chris@297 142 {
Chris@1582 143 return nullptr;
Chris@297 144 }
Chris@297 145
Chris@1737 146 ModelId
Chris@297 147 AlignmentModel::getReferenceModel() const
Chris@297 148 {
Chris@297 149 return m_reference;
Chris@297 150 }
Chris@297 151
Chris@1737 152 ModelId
Chris@297 153 AlignmentModel::getAlignedModel() const
Chris@297 154 {
Chris@297 155 return m_aligned;
Chris@297 156 }
Chris@297 157
Chris@1038 158 sv_frame_t
Chris@1038 159 AlignmentModel::toReference(sv_frame_t frame) const
Chris@297 160 {
Chris@376 161 #ifdef DEBUG_ALIGNMENT_MODEL
Chris@1075 162 cerr << "AlignmentModel::toReference(" << frame << ")" << endl;
Chris@376 163 #endif
Chris@371 164 if (!m_path) {
Chris@1738 165 if (m_pathSource.isNone()) {
Chris@1738 166 return frame;
Chris@1738 167 }
Chris@371 168 constructPath();
Chris@371 169 }
Chris@1738 170 if (!m_path) {
Chris@1738 171 return frame;
Chris@1738 172 }
Chris@1738 173
Chris@1738 174 return performAlignment(*m_path, frame);
Chris@297 175 }
Chris@297 176
Chris@1038 177 sv_frame_t
Chris@1038 178 AlignmentModel::fromReference(sv_frame_t frame) const
Chris@297 179 {
Chris@376 180 #ifdef DEBUG_ALIGNMENT_MODEL
Chris@1075 181 cerr << "AlignmentModel::fromReference(" << frame << ")" << endl;
Chris@376 182 #endif
Chris@371 183 if (!m_reversePath) {
Chris@1738 184 if (m_pathSource.isNone()) {
Chris@1738 185 return frame;
Chris@1738 186 }
Chris@371 187 constructReversePath();
Chris@371 188 }
Chris@1738 189 if (!m_reversePath) {
Chris@1738 190 return frame;
Chris@1738 191 }
Chris@297 192
Chris@1738 193 return performAlignment(*m_reversePath, frame);
Chris@297 194 }
Chris@297 195
Chris@297 196 void
Chris@1735 197 AlignmentModel::pathSourceChangedWithin(sv_frame_t, sv_frame_t)
Chris@297 198 {
Chris@297 199 if (!m_pathComplete) return;
Chris@338 200 constructPath();
Chris@297 201 constructReversePath();
Chris@297 202 }
Chris@297 203
Chris@297 204 void
Chris@1735 205 AlignmentModel::pathSourceCompletionChanged()
Chris@297 206 {
Chris@1737 207 auto pathSourceModel =
Chris@1737 208 ModelById::getAs<SparseTimeValueModel>(m_pathSource);
Chris@1737 209 if (!pathSourceModel) return;
Chris@1737 210
Chris@323 211 m_pathBegun = true;
Chris@323 212
Chris@297 213 if (!m_pathComplete) {
Chris@338 214
Chris@297 215 int completion = 0;
Chris@1737 216 pathSourceModel->isReady(&completion);
Chris@338 217
Chris@376 218 #ifdef DEBUG_ALIGNMENT_MODEL
Chris@1666 219 SVCERR << "AlignmentModel::pathCompletionChanged: completion = "
Chris@1666 220 << completion << endl;
Chris@376 221 #endif
Chris@338 222
Chris@323 223 m_pathComplete = (completion == 100);
Chris@338 224
Chris@297 225 if (m_pathComplete) {
Chris@338 226
Chris@338 227 constructPath();
Chris@297 228 constructReversePath();
Chris@1666 229
Chris@1691 230 #ifdef DEBUG_ALIGNMENT_MODEL
Chris@1691 231 SVCERR << "AlignmentModel: path complete" << endl;
Chris@1691 232 #endif
Chris@297 233 }
Chris@297 234 }
Chris@323 235
Chris@297 236 emit completionChanged();
Chris@297 237 }
Chris@297 238
Chris@297 239 void
Chris@338 240 AlignmentModel::constructPath() const
Chris@297 241 {
Chris@1737 242 auto alignedModel = ModelById::get(m_aligned);
Chris@1737 243 if (!alignedModel) return;
Chris@1737 244
Chris@1737 245 auto pathSourceModel =
Chris@1737 246 ModelById::getAs<SparseTimeValueModel>(m_pathSource);
Chris@338 247 if (!m_path) {
Chris@1737 248 if (pathSourceModel) {
Chris@843 249 cerr << "ERROR: AlignmentModel::constructPath: "
Chris@843 250 << "No raw path available" << endl;
Chris@338 251 return;
Chris@338 252 }
Chris@1738 253 m_path.reset(new Path
Chris@1737 254 (pathSourceModel->getSampleRate(),
Chris@1738 255 pathSourceModel->getResolution()));
Chris@338 256 } else {
Chris@1737 257 if (!pathSourceModel) return;
Chris@297 258 }
Chris@297 259
Chris@338 260 m_path->clear();
Chris@297 261
Chris@1737 262 EventVector points = pathSourceModel->getAllEvents();
Chris@1651 263
Chris@1651 264 for (const auto &p: points) {
Chris@1651 265 sv_frame_t frame = p.getFrame();
Chris@1651 266 double value = p.getValue();
Chris@1737 267 sv_frame_t rframe = lrint(value * alignedModel->getSampleRate());
Chris@1662 268 m_path->add(PathPoint(frame, rframe));
Chris@297 269 }
Chris@297 270
Chris@376 271 #ifdef DEBUG_ALIGNMENT_MODEL
Chris@1075 272 cerr << "AlignmentModel::constructPath: " << m_path->getPointCount() << " points, at least " << (2 * m_path->getPointCount() * (3 * sizeof(void *) + sizeof(int) + sizeof(PathPoint))) << " bytes" << endl;
Chris@376 273 #endif
Chris@338 274 }
Chris@338 275
Chris@338 276 void
Chris@338 277 AlignmentModel::constructReversePath() const
Chris@338 278 {
Chris@338 279 if (!m_reversePath) {
Chris@407 280 if (!m_path) {
Chris@843 281 cerr << "ERROR: AlignmentModel::constructReversePath: "
Chris@843 282 << "No forward path available" << endl;
Chris@407 283 return;
Chris@407 284 }
Chris@1738 285 m_reversePath.reset(new Path
Chris@1737 286 (m_path->getSampleRate(),
Chris@1738 287 m_path->getResolution()));
Chris@338 288 } else {
Chris@407 289 if (!m_path) return;
Chris@338 290 }
Chris@338 291
Chris@338 292 m_reversePath->clear();
Chris@407 293
Chris@1738 294 Path::Points points = m_path->getPoints();
Chris@407 295
Chris@1738 296 for (auto p: points) {
Chris@1738 297 sv_frame_t frame = p.frame;
Chris@1738 298 sv_frame_t rframe = p.mapframe;
Chris@1662 299 m_reversePath->add(PathPoint(rframe, frame));
Chris@407 300 }
Chris@338 301
Chris@376 302 #ifdef DEBUG_ALIGNMENT_MODEL
Chris@1075 303 cerr << "AlignmentModel::constructReversePath: " << m_reversePath->getPointCount() << " points, at least " << (2 * m_reversePath->getPointCount() * (3 * sizeof(void *) + sizeof(int) + sizeof(PathPoint))) << " bytes" << endl;
Chris@376 304 #endif
Chris@297 305 }
Chris@297 306
Chris@1038 307 sv_frame_t
Chris@1738 308 AlignmentModel::performAlignment(const Path &path, sv_frame_t frame) const
Chris@297 309 {
Chris@338 310 // The path consists of a series of points, each with frame equal
Chris@338 311 // to the frame on the source model and mapframe equal to the
Chris@338 312 // frame on the target model. Both should be monotonically
Chris@338 313 // increasing.
Chris@297 314
Chris@1738 315 const Path::Points &points = path.getPoints();
Chris@297 316
Chris@297 317 if (points.empty()) {
Chris@379 318 #ifdef DEBUG_ALIGNMENT_MODEL
Chris@1075 319 cerr << "AlignmentModel::align: No points" << endl;
Chris@379 320 #endif
Chris@297 321 return frame;
Chris@297 322 }
Chris@297 323
Chris@376 324 #ifdef DEBUG_ALIGNMENT_MODEL
Chris@1075 325 cerr << "AlignmentModel::align: frame " << frame << " requested" << endl;
Chris@376 326 #endif
Chris@376 327
Chris@1662 328 PathPoint point(frame);
Chris@1738 329 Path::Points::const_iterator i = points.lower_bound(point);
Chris@376 330 if (i == points.end()) {
Chris@376 331 #ifdef DEBUG_ALIGNMENT_MODEL
Chris@843 332 cerr << "Note: i == points.end()" << endl;
Chris@376 333 #endif
Chris@376 334 --i;
Chris@376 335 }
Chris@1738 336 while (i != points.begin() && i->frame > frame) {
Chris@1738 337 --i;
Chris@1738 338 }
Chris@297 339
Chris@1038 340 sv_frame_t foundFrame = i->frame;
Chris@1038 341 sv_frame_t foundMapFrame = i->mapframe;
Chris@297 342
Chris@1038 343 sv_frame_t followingFrame = foundFrame;
Chris@1038 344 sv_frame_t followingMapFrame = foundMapFrame;
Chris@297 345
Chris@312 346 if (++i != points.end()) {
Chris@376 347 #ifdef DEBUG_ALIGNMENT_MODEL
Chris@843 348 cerr << "another point available" << endl;
Chris@376 349 #endif
Chris@312 350 followingFrame = i->frame;
Chris@338 351 followingMapFrame = i->mapframe;
Chris@376 352 } else {
Chris@376 353 #ifdef DEBUG_ALIGNMENT_MODEL
Chris@843 354 cerr << "no other point available" << endl;
Chris@376 355 #endif
Chris@376 356 }
Chris@312 357
Chris@1075 358 #ifdef DEBUG_ALIGNMENT_MODEL
Chris@1075 359 cerr << "foundFrame = " << foundFrame << ", foundMapFrame = " << foundMapFrame
Chris@1075 360 << ", followingFrame = " << followingFrame << ", followingMapFrame = "
Chris@1075 361 << followingMapFrame << endl;
Chris@1075 362 #endif
Chris@1075 363
Chris@1738 364 if (foundMapFrame < 0) {
Chris@1738 365 return 0;
Chris@1738 366 }
Chris@312 367
Chris@1038 368 sv_frame_t resultFrame = foundMapFrame;
Chris@312 369
Chris@1038 370 if (followingFrame != foundFrame && frame > foundFrame) {
Chris@1038 371 double interp =
Chris@1038 372 double(frame - foundFrame) /
Chris@1038 373 double(followingFrame - foundFrame);
Chris@1038 374 resultFrame += lrint(double(followingMapFrame - foundMapFrame) * interp);
Chris@312 375 }
Chris@312 376
Chris@376 377 #ifdef DEBUG_ALIGNMENT_MODEL
Chris@1075 378 cerr << "AlignmentModel::align: resultFrame = " << resultFrame << endl;
Chris@376 379 #endif
Chris@312 380
Chris@312 381 return resultFrame;
Chris@297 382 }
Chris@407 383
Chris@407 384 void
Chris@1737 385 AlignmentModel::setPathFrom(ModelId pathSource)
Chris@1016 386 {
Chris@1737 387 m_pathSource = pathSource;
Chris@1737 388
Chris@1737 389 auto pathSourceModel =
Chris@1737 390 ModelById::getAs<SparseTimeValueModel>(m_pathSource);
Chris@1737 391
Chris@1737 392 if (pathSourceModel) {
Chris@1016 393
Chris@1737 394 connect(pathSourceModel.get(),
Chris@1737 395 SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
Chris@1737 396 this, SLOT(pathSourceChangedWithin(sv_frame_t, sv_frame_t)));
Chris@1737 397
Chris@1737 398 connect(pathSourceModel.get(), SIGNAL(completionChanged()),
Chris@1737 399 this, SLOT(pathSourceCompletionChanged()));
Chris@1737 400
Chris@1737 401 constructPath();
Chris@1737 402 constructReversePath();
Chris@1737 403
Chris@1737 404 if (pathSourceModel->isReady()) {
Chris@1737 405 pathSourceCompletionChanged();
Chris@1737 406 }
Chris@1712 407 }
Chris@1016 408 }
Chris@1016 409
Chris@1016 410 void
Chris@1738 411 AlignmentModel::setPath(const Path &path)
Chris@407 412 {
Chris@1738 413 m_path.reset(new Path(path));
Chris@1560 414 m_pathComplete = true;
Chris@407 415 constructReversePath();
Chris@407 416 }
Chris@297 417
Chris@407 418 void
Chris@407 419 AlignmentModel::toXml(QTextStream &stream,
Chris@407 420 QString indent,
Chris@407 421 QString extraAttributes) const
Chris@407 422 {
Chris@407 423 if (!m_path) {
Chris@690 424 SVDEBUG << "AlignmentModel::toXml: no path" << endl;
Chris@407 425 return;
Chris@407 426 }
Chris@407 427
Chris@407 428 m_path->toXml(stream, indent, "");
Chris@407 429
Chris@407 430 Model::toXml(stream, indent,
Chris@407 431 QString("type=\"alignment\" reference=\"%1\" aligned=\"%2\" path=\"%3\" %4")
Chris@1738 432 .arg(ModelById::getExportId(m_reference))
Chris@1738 433 .arg(ModelById::getExportId(m_aligned))
Chris@1677 434 .arg(m_path->getExportId())
Chris@407 435 .arg(extraAttributes));
Chris@407 436 }