annotate data/model/AlignmentModel.cpp @ 1881:b504df98c3be

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