annotate data/model/AlignmentModel.cpp @ 1804:343ef2a866a4

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