annotate data/model/AlignmentModel.cpp @ 1752:6d09d68165a4 by-id

Further review of ById: make IDs only available when adding a model to the ById store, not by querying the item directly. This means any id encountered in the wild must have been added to the store at some point (even if later released), which simplifies reasoning about lifecycles
author Chris Cannam
date Fri, 05 Jul 2019 15:28:07 +0100
parents 4abc0f08adf9
children 9945ad04c174
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@1752 197 AlignmentModel::pathSourceChangedWithin(ModelId, 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@1752 205 AlignmentModel::pathSourceCompletionChanged(ModelId)
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@1752 236 emit completionChanged(getId());
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@1752 395 SIGNAL(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t)),
Chris@1752 396 this, SLOT(pathSourceChangedWithin(ModelId, sv_frame_t, sv_frame_t)));
Chris@1737 397
Chris@1752 398 connect(pathSourceModel.get(), SIGNAL(completionChanged(ModelId)),
Chris@1752 399 this, SLOT(pathSourceCompletionChanged(ModelId)));
Chris@1737 400
Chris@1737 401 constructPath();
Chris@1737 402 constructReversePath();
Chris@1737 403
Chris@1737 404 if (pathSourceModel->isReady()) {
Chris@1752 405 pathSourceCompletionChanged(m_pathSource);
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 }