annotate framework/Align.cpp @ 686:610fa108fbcc by-id

Update for changes to PlayParameters
author Chris Cannam
date Thu, 04 Jul 2019 18:04:59 +0100
parents 0736beb8b852
children e0b0f3e163ca
rev   line source
Chris@420 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@420 2
Chris@420 3 /*
Chris@420 4 Sonic Visualiser
Chris@420 5 An audio file viewer and annotation editor.
Chris@420 6 Centre for Digital Music, Queen Mary, University of London.
Chris@420 7
Chris@420 8 This program is free software; you can redistribute it and/or
Chris@420 9 modify it under the terms of the GNU General Public License as
Chris@420 10 published by the Free Software Foundation; either version 2 of the
Chris@420 11 License, or (at your option) any later version. See the file
Chris@420 12 COPYING included with this distribution for more information.
Chris@420 13 */
Chris@420 14
Chris@420 15 #include "Align.h"
Chris@665 16 #include "Document.h"
Chris@420 17
Chris@420 18 #include "data/model/WaveFileModel.h"
Chris@515 19 #include "data/model/ReadOnlyWaveFileModel.h"
Chris@420 20 #include "data/model/AggregateWaveModel.h"
Chris@420 21 #include "data/model/RangeSummarisableTimeValueModel.h"
Chris@420 22 #include "data/model/SparseTimeValueModel.h"
Chris@420 23 #include "data/model/AlignmentModel.h"
Chris@420 24
Chris@420 25 #include "data/fileio/CSVFileReader.h"
Chris@420 26
Chris@420 27 #include "transform/TransformFactory.h"
Chris@420 28 #include "transform/ModelTransformerFactory.h"
Chris@420 29 #include "transform/FeatureExtractionModelTransformer.h"
Chris@420 30
Chris@420 31 #include <QProcess>
Chris@422 32 #include <QSettings>
Chris@430 33 #include <QApplication>
Chris@422 34
Chris@422 35 bool
Chris@683 36 Align::alignModel(Document *doc, ModelId ref, ModelId other, QString &error)
Chris@422 37 {
Chris@422 38 QSettings settings;
Chris@422 39 settings.beginGroup("Preferences");
Chris@422 40 bool useProgram = settings.value("use-external-alignment", false).toBool();
Chris@422 41 QString program = settings.value("external-alignment-program", "").toString();
Chris@422 42 settings.endGroup();
Chris@422 43
Chris@422 44 if (useProgram && (program != "")) {
Chris@670 45 return alignModelViaProgram(doc, ref, other, program, error);
Chris@422 46 } else {
Chris@670 47 return alignModelViaTransform(doc, ref, other, error);
Chris@422 48 }
Chris@422 49 }
Chris@420 50
Chris@428 51 QString
Chris@428 52 Align::getAlignmentTransformName()
Chris@428 53 {
Chris@428 54 QSettings settings;
Chris@428 55 settings.beginGroup("Alignment");
Chris@428 56 TransformId id =
Chris@428 57 settings.value("transform-id",
Chris@428 58 "vamp:match-vamp-plugin:match:path").toString();
Chris@428 59 settings.endGroup();
Chris@428 60 return id;
Chris@428 61 }
Chris@428 62
Chris@670 63 QString
Chris@670 64 Align::getTuningDifferenceTransformName()
Chris@670 65 {
Chris@670 66 QSettings settings;
Chris@670 67 settings.beginGroup("Alignment");
Chris@670 68 bool performPitchCompensation =
Chris@670 69 settings.value("align-pitch-aware", false).toBool();
Chris@670 70 QString id = "";
Chris@671 71 if (performPitchCompensation) {
Chris@670 72 id = settings.value
Chris@670 73 ("tuning-difference-transform-id",
Chris@670 74 "vamp:tuning-difference:tuning-difference:tuningfreq")
Chris@670 75 .toString();
Chris@671 76 }
Chris@670 77 settings.endGroup();
Chris@670 78 return id;
Chris@670 79 }
Chris@670 80
Chris@428 81 bool
Chris@428 82 Align::canAlign()
Chris@428 83 {
Chris@670 84 TransformFactory *factory = TransformFactory::getInstance();
Chris@428 85 TransformId id = getAlignmentTransformName();
Chris@670 86 TransformId tdId = getTuningDifferenceTransformName();
Chris@670 87 return factory->haveTransform(id) &&
Chris@670 88 (tdId == "" || factory->haveTransform(tdId));
Chris@428 89 }
Chris@428 90
Chris@420 91 bool
Chris@683 92 Align::alignModelViaTransform(Document *doc, ModelId ref, ModelId other,
Chris@670 93 QString &error)
Chris@420 94 {
Chris@670 95 QMutexLocker locker (&m_mutex);
Chris@420 96
Chris@683 97 auto reference = ModelById::getAs<RangeSummarisableTimeValueModel>(ref);
Chris@683 98 auto rm = ModelById::getAs<RangeSummarisableTimeValueModel>(other);
Chris@683 99 if (!reference || !rm) return false;
Chris@420 100
Chris@670 101 // This involves creating either three or four new models:
Chris@672 102 //
Chris@420 103 // 1. an AggregateWaveModel to provide the mixdowns of the main
Chris@420 104 // model and the new model in its two channels, as input to the
Chris@420 105 // MATCH plugin
Chris@672 106 //
Chris@670 107 // 2a. a SparseTimeValueModel which will be automatically created
Chris@670 108 // by FeatureExtractionModelTransformer when running the
Chris@670 109 // TuningDifference plugin to receive the relative tuning of the
Chris@670 110 // second model (if pitch-aware alignment is enabled in the
Chris@670 111 // preferences)
Chris@672 112 //
Chris@670 113 // 2b. a SparseTimeValueModel which will be automatically created
Chris@670 114 // by FeatureExtractionPluginTransformer when running the MATCH
Chris@670 115 // plugin to perform alignment (so containing the alignment path)
Chris@672 116 //
Chris@420 117 // 3. an AlignmentModel, which stores the path model and carries
Chris@420 118 // out alignment lookups on it.
Chris@672 119 //
Chris@670 120 // The AggregateWaveModel [1] is registered with the document,
Chris@670 121 // which deletes it when it is invalidated (when one of its
Chris@670 122 // components is deleted). The SparseTimeValueModel [2a] is reused
Chris@670 123 // by us when starting the alignment process proper, and is then
Chris@670 124 // deleted by us. The SparseTimeValueModel [2b] is passed to the
Chris@670 125 // AlignmentModel, which takes ownership of it. The AlignmentModel
Chris@670 126 // is attached to the new model we are aligning, which also takes
Chris@670 127 // ownership of it. The only one of these models that we need to
Chris@670 128 // delete here is the SparseTimeValueModel [2a].
Chris@683 129 //!!! todo: review the above, especially management of AlignmentModel
Chris@672 130 //
Chris@672 131 // (We also create a sneaky additional SparseTimeValueModel
Chris@672 132 // temporarily so we can attach completion information to it -
Chris@672 133 // this is quite unnecessary from the perspective of simply
Chris@672 134 // producing the results.)
Chris@420 135
Chris@420 136 AggregateWaveModel::ChannelSpecList components;
Chris@420 137
Chris@420 138 components.push_back(AggregateWaveModel::ModelChannelSpec
Chris@681 139 (reference->getId(), -1));
Chris@420 140
Chris@420 141 components.push_back(AggregateWaveModel::ModelChannelSpec
Chris@681 142 (rm->getId(), -1));
Chris@420 143
Chris@683 144 auto aggregateModel = std::make_shared<AggregateWaveModel>(components);
Chris@683 145 ModelById::add(aggregateModel);
Chris@683 146 doc->addAggregateModel(aggregateModel->getId());
Chris@670 147
Chris@683 148 auto alignmentModel = std::make_shared<AlignmentModel>(ref, other,
Chris@683 149 ModelId());
Chris@683 150 ModelById::add(alignmentModel);
Chris@670 151
Chris@670 152 TransformId tdId = getTuningDifferenceTransformName();
Chris@670 153
Chris@670 154 if (tdId == "") {
Chris@670 155
Chris@683 156 if (beginTransformDrivenAlignment(aggregateModel->getId(),
Chris@683 157 alignmentModel->getId())) {
Chris@683 158 rm->setAlignment(alignmentModel->getId());
Chris@670 159 } else {
Chris@670 160 error = alignmentModel->getError();
Chris@683 161 ModelById::release(alignmentModel);
Chris@670 162 return false;
Chris@670 163 }
Chris@670 164
Chris@670 165 } else {
Chris@670 166
Chris@670 167 // Have a tuning-difference transform id, so run it
Chris@670 168 // asynchronously first
Chris@670 169
Chris@670 170 TransformFactory *tf = TransformFactory::getInstance();
Chris@670 171
Chris@670 172 Transform transform = tf->getDefaultTransformFor
Chris@670 173 (tdId, aggregateModel->getSampleRate());
Chris@670 174
Chris@678 175 transform.setParameter("maxduration", 60);
Chris@678 176 transform.setParameter("maxrange", 6);
Chris@678 177 transform.setParameter("finetuning", false);
Chris@671 178
Chris@670 179 SVDEBUG << "Align::alignModel: Tuning difference transform step size " << transform.getStepSize() << ", block size " << transform.getBlockSize() << endl;
Chris@670 180
Chris@670 181 ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance();
Chris@670 182
Chris@670 183 QString message;
Chris@683 184 ModelId transformOutput = mtf->transform(transform,
Chris@683 185 aggregateModel->getId(),
Chris@683 186 message);
Chris@670 187
Chris@683 188 auto tdout = ModelById::getAs<SparseTimeValueModel>(transformOutput);
Chris@670 189 if (!tdout) {
Chris@670 190 SVCERR << "Align::alignModel: ERROR: Failed to create tuning-difference output model (no Tuning Difference plugin?)" << endl;
Chris@670 191 error = message;
Chris@670 192 return false;
Chris@670 193 }
Chris@670 194
Chris@683 195 rm->setAlignment(alignmentModel->getId());
Chris@665 196
Chris@683 197 connect(tdout.get(), SIGNAL(completionChanged()),
Chris@670 198 this, SLOT(tuningDifferenceCompletionChanged()));
Chris@420 199
Chris@671 200 TuningDiffRec rec;
Chris@683 201 rec.input = aggregateModel->getId();
Chris@683 202 rec.alignment = alignmentModel->getId();
Chris@677 203
Chris@671 204 // This model exists only so that the AlignmentModel can get a
Chris@671 205 // completion value from somewhere while the tuning difference
Chris@671 206 // calculation is going on
Chris@683 207 auto preparatoryModel = std::make_shared<SparseTimeValueModel>
Chris@683 208 (aggregateModel->getSampleRate(), 1);
Chris@683 209 ModelById::add(preparatoryModel);
Chris@683 210 preparatoryModel->setCompletion(0);
Chris@683 211 rec.preparatory = preparatoryModel->getId();
Chris@671 212 alignmentModel->setPathFrom(rec.preparatory);
Chris@671 213
Chris@683 214 m_pendingTuningDiffs[transformOutput] = rec;
Chris@670 215 }
Chris@670 216
Chris@670 217 return true;
Chris@670 218 }
Chris@670 219
Chris@671 220 void
Chris@671 221 Align::tuningDifferenceCompletionChanged()
Chris@671 222 {
Chris@671 223 QMutexLocker locker (&m_mutex);
Chris@671 224
Chris@683 225 ModelId tdId;
Chris@683 226 if (Model *modelPtr = qobject_cast<Model *>(sender())) {
Chris@683 227 tdId = modelPtr->getId();
Chris@683 228 } else {
Chris@671 229 return;
Chris@671 230 }
Chris@671 231
Chris@683 232 if (m_pendingTuningDiffs.find(tdId) == m_pendingTuningDiffs.end()) {
Chris@683 233 SVCERR << "ERROR: Align::tuningDifferenceCompletionChanged: Model "
Chris@683 234 << tdId << " not found in pending tuning diff map!" << endl;
Chris@683 235 return;
Chris@683 236 }
Chris@671 237
Chris@683 238 auto td = ModelById::getAs<SparseTimeValueModel>(tdId);
Chris@683 239 if (!td) {
Chris@683 240 SVCERR << "WARNING: Align::tuningDifferenceCompletionChanged: Model "
Chris@683 241 << tdId << " not known as SparseTimeValueModel" << endl;
Chris@683 242 return;
Chris@683 243 }
Chris@683 244
Chris@683 245 TuningDiffRec rec = m_pendingTuningDiffs[tdId];
Chris@683 246
Chris@683 247 auto alignment = ModelById::getAs<AlignmentModel>(rec.alignment);
Chris@683 248 if (!alignment) {
Chris@683 249 SVCERR << "WARNING: Align::tuningDifferenceCompletionChanged:"
Chris@683 250 << "alignment model has disappeared" << endl;
Chris@683 251 return;
Chris@683 252 }
Chris@683 253
Chris@671 254 int completion = 0;
Chris@671 255 bool done = td->isReady(&completion);
Chris@671 256
Chris@674 257 // SVCERR << "Align::tuningDifferenceCompletionChanged: done = " << done << ", completion = " << completion << endl;
Chris@671 258
Chris@671 259 if (!done) {
Chris@671 260 // This will be the completion the alignment model reports,
Chris@671 261 // before the alignment actually begins. It goes up from 0 to
Chris@671 262 // 99 (not 100!) and then back to 0 again when we start
Chris@671 263 // calculating the actual path in the following phase
Chris@671 264 int clamped = (completion == 100 ? 99 : completion);
Chris@674 265 // SVCERR << "Align::tuningDifferenceCompletionChanged: setting rec.preparatory completion to " << clamped << endl;
Chris@683 266 auto preparatory = ModelById::getAs<SparseTimeValueModel>
Chris@683 267 (rec.preparatory);
Chris@683 268 if (preparatory) {
Chris@683 269 preparatory->setCompletion(clamped);
Chris@683 270 }
Chris@671 271 return;
Chris@671 272 }
Chris@671 273
Chris@671 274 float tuningFrequency = 440.f;
Chris@671 275
Chris@671 276 if (!td->isEmpty()) {
Chris@671 277 tuningFrequency = td->getAllEvents()[0].getValue();
Chris@671 278 SVCERR << "Align::tuningDifferenceCompletionChanged: Reported tuning frequency = " << tuningFrequency << endl;
Chris@671 279 } else {
Chris@671 280 SVCERR << "Align::tuningDifferenceCompletionChanged: No tuning frequency reported" << endl;
Chris@671 281 }
Chris@671 282
Chris@683 283 m_pendingTuningDiffs.erase(tdId);
Chris@683 284 ModelById::release(tdId);
Chris@683 285
Chris@683 286 alignment->setPathFrom({});
Chris@671 287
Chris@671 288 beginTransformDrivenAlignment
Chris@671 289 (rec.input, rec.alignment, tuningFrequency);
Chris@671 290 }
Chris@671 291
Chris@670 292 bool
Chris@683 293 Align::beginTransformDrivenAlignment(ModelId aggregateModelId,
Chris@683 294 ModelId alignmentModelId,
Chris@670 295 float tuningFrequency)
Chris@670 296 {
Chris@428 297 TransformId id = getAlignmentTransformName();
Chris@420 298
Chris@420 299 TransformFactory *tf = TransformFactory::getInstance();
Chris@420 300
Chris@683 301 auto aggregateModel = ModelById::getAs<AggregateWaveModel>(aggregateModelId);
Chris@683 302 auto alignmentModel = ModelById::getAs<AlignmentModel>(alignmentModelId);
Chris@683 303
Chris@683 304 if (!aggregateModel || !alignmentModel) {
Chris@683 305 SVCERR << "Align::alignModel: ERROR: One or other of the aggregate & alignment models has disappeared" << endl;
Chris@683 306 return false;
Chris@683 307 }
Chris@683 308
Chris@420 309 Transform transform = tf->getDefaultTransformFor
Chris@420 310 (id, aggregateModel->getSampleRate());
Chris@420 311
Chris@420 312 transform.setStepSize(transform.getBlockSize()/2);
Chris@420 313 transform.setParameter("serialise", 1);
Chris@420 314 transform.setParameter("smooth", 0);
Chris@420 315
Chris@670 316 if (tuningFrequency != 0.f) {
Chris@670 317 transform.setParameter("freq2", tuningFrequency);
Chris@670 318 }
Chris@670 319
Chris@420 320 SVDEBUG << "Align::alignModel: Alignment transform step size " << transform.getStepSize() << ", block size " << transform.getBlockSize() << endl;
Chris@420 321
Chris@420 322 ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance();
Chris@420 323
Chris@420 324 QString message;
Chris@683 325 ModelId transformOutput = mtf->transform
Chris@683 326 (transform, aggregateModelId, message);
Chris@420 327
Chris@683 328 if (transformOutput.isNone()) {
Chris@420 329 transform.setStepSize(0);
Chris@670 330 transformOutput = mtf->transform
Chris@683 331 (transform, aggregateModelId, message);
Chris@420 332 }
Chris@420 333
Chris@683 334 auto path = ModelById::getAs<SparseTimeValueModel>(transformOutput);
Chris@420 335
Chris@670 336 //!!! callers will need to be updated to get error from
Chris@670 337 //!!! alignment model after initial call
Chris@670 338
Chris@420 339 if (!path) {
Chris@649 340 SVCERR << "Align::alignModel: ERROR: Failed to create alignment path (no MATCH plugin?)" << endl;
Chris@683 341 ModelById::release(transformOutput);
Chris@670 342 alignmentModel->setError(message);
Chris@420 343 return false;
Chris@420 344 }
Chris@420 345
Chris@420 346 path->setCompletion(0);
Chris@683 347 alignmentModel->setPathFrom(transformOutput); //!!! who releases transformOutput?
Chris@420 348
Chris@683 349 connect(alignmentModel.get(), SIGNAL(completionChanged()),
Chris@428 350 this, SLOT(alignmentCompletionChanged()));
Chris@420 351
Chris@420 352 return true;
Chris@420 353 }
Chris@420 354
Chris@428 355 void
Chris@428 356 Align::alignmentCompletionChanged()
Chris@428 357 {
Chris@670 358 QMutexLocker locker (&m_mutex);
Chris@683 359
Chris@683 360 if (AlignmentModel *amPtr = qobject_cast<AlignmentModel *>(sender())) {
Chris@683 361
Chris@683 362 auto am = ModelById::getAs<AlignmentModel>(amPtr->getId());
Chris@683 363 if (am && am->isReady()) {
Chris@683 364 disconnect(am.get(), SIGNAL(completionChanged()),
Chris@683 365 this, SLOT(alignmentCompletionChanged()));
Chris@683 366 emit alignmentComplete(am->getId());
Chris@683 367 }
Chris@428 368 }
Chris@428 369 }
Chris@428 370
Chris@420 371 bool
Chris@683 372 Align::alignModelViaProgram(Document *, ModelId ref, ModelId other,
Chris@670 373 QString program, QString &error)
Chris@420 374 {
Chris@670 375 QMutexLocker locker (&m_mutex);
Chris@420 376
Chris@683 377 auto reference = ModelById::getAs<RangeSummarisableTimeValueModel>(ref);
Chris@683 378 auto rm = ModelById::getAs<RangeSummarisableTimeValueModel>(other);
Chris@683 379 if (!reference || !rm) return false;
Chris@420 380
Chris@636 381 while (!reference->isReady(nullptr) || !rm->isReady(nullptr)) {
Chris@430 382 qApp->processEvents();
Chris@430 383 }
Chris@430 384
Chris@420 385 // Run an external program, passing to it paths to the main
Chris@420 386 // model's audio file and the new model's audio file. It returns
Chris@420 387 // the path in CSV form through stdout.
Chris@420 388
Chris@683 389 auto roref = ModelById::getAs<ReadOnlyWaveFileModel>(ref);
Chris@683 390 auto rorm = ModelById::getAs<ReadOnlyWaveFileModel>(other);
Chris@515 391 if (!roref || !rorm) {
Chris@649 392 SVCERR << "ERROR: Align::alignModelViaProgram: Can't align non-read-only models via program (no local filename available)" << endl;
Chris@515 393 return false;
Chris@515 394 }
Chris@515 395
Chris@515 396 QString refPath = roref->getLocalFilename();
Chris@515 397 QString otherPath = rorm->getLocalFilename();
Chris@420 398
Chris@420 399 if (refPath == "" || otherPath == "") {
Chris@670 400 error = "Failed to find local filepath for wave-file model";
Chris@595 401 return false;
Chris@420 402 }
Chris@420 403
Chris@683 404 auto alignmentModel = std::make_shared<AlignmentModel>(ref, other,
Chris@683 405 ModelId());
Chris@683 406 ModelById::add(alignmentModel);
Chris@683 407 rm->setAlignment(alignmentModel->getId());
Chris@423 408
Chris@423 409 QProcess *process = new QProcess;
Chris@420 410 QStringList args;
Chris@420 411 args << refPath << otherPath;
Chris@423 412
Chris@423 413 connect(process, SIGNAL(finished(int, QProcess::ExitStatus)),
Chris@423 414 this, SLOT(alignmentProgramFinished(int, QProcess::ExitStatus)));
Chris@420 415
Chris@683 416 m_pendingProcesses[process] = alignmentModel->getId();
Chris@423 417 process->start(program, args);
Chris@420 418
Chris@423 419 bool success = process->waitForStarted();
Chris@423 420
Chris@423 421 if (!success) {
Chris@649 422 SVCERR << "ERROR: Align::alignModelViaProgram: Program did not start"
Chris@649 423 << endl;
Chris@670 424 error = "Alignment program could not be started";
Chris@670 425 m_pendingProcesses.erase(process);
Chris@683 426 //!!! who releases alignmentModel? does this? review
Chris@683 427 rm->setAlignment({});
Chris@423 428 delete process;
Chris@423 429 }
Chris@423 430
Chris@423 431 return success;
Chris@423 432 }
Chris@423 433
Chris@423 434 void
Chris@423 435 Align::alignmentProgramFinished(int exitCode, QProcess::ExitStatus status)
Chris@423 436 {
Chris@670 437 QMutexLocker locker (&m_mutex);
Chris@670 438
Chris@649 439 SVCERR << "Align::alignmentProgramFinished" << endl;
Chris@423 440
Chris@423 441 QProcess *process = qobject_cast<QProcess *>(sender());
Chris@423 442
Chris@670 443 if (m_pendingProcesses.find(process) == m_pendingProcesses.end()) {
Chris@649 444 SVCERR << "ERROR: Align::alignmentProgramFinished: Process " << process
Chris@649 445 << " not found in process model map!" << endl;
Chris@423 446 return;
Chris@423 447 }
Chris@423 448
Chris@683 449 ModelId alignmentModelId = m_pendingProcesses[process];
Chris@683 450 auto alignmentModel = ModelById::getAs<AlignmentModel>(alignmentModelId);
Chris@683 451 if (!alignmentModel) return;
Chris@423 452
Chris@423 453 if (exitCode == 0 && status == 0) {
Chris@420 454
Chris@595 455 CSVFormat format;
Chris@595 456 format.setModelType(CSVFormat::TwoDimensionalModel);
Chris@595 457 format.setTimingType(CSVFormat::ExplicitTiming);
Chris@595 458 format.setTimeUnits(CSVFormat::TimeSeconds);
Chris@595 459 format.setColumnCount(2);
Chris@425 460 // The output format has time in the reference file first, and
Chris@425 461 // time in the "other" file in the second column. This is a
Chris@425 462 // more natural approach for a command-line alignment tool,
Chris@425 463 // but it's the opposite of what we expect for native
Chris@425 464 // alignment paths, which map from "other" file to
Chris@425 465 // reference. These column purpose settings reflect that.
Chris@595 466 format.setColumnPurpose(1, CSVFormat::ColumnStartTime);
Chris@595 467 format.setColumnPurpose(0, CSVFormat::ColumnValue);
Chris@595 468 format.setAllowQuoting(false);
Chris@595 469 format.setSeparator(',');
Chris@420 470
Chris@595 471 CSVFileReader reader(process, format, alignmentModel->getSampleRate());
Chris@595 472 if (!reader.isOK()) {
Chris@649 473 SVCERR << "ERROR: Align::alignmentProgramFinished: Failed to parse output"
Chris@649 474 << endl;
Chris@670 475 alignmentModel->setError
Chris@670 476 (QString("Failed to parse output of program: %1")
Chris@670 477 .arg(reader.getError()));
Chris@423 478 goto done;
Chris@595 479 }
Chris@420 480
Chris@683 481 //!!! to use ById?
Chris@683 482
Chris@595 483 Model *csvOutput = reader.load();
Chris@420 484
Chris@595 485 SparseTimeValueModel *path = qobject_cast<SparseTimeValueModel *>(csvOutput);
Chris@595 486 if (!path) {
Chris@649 487 SVCERR << "ERROR: Align::alignmentProgramFinished: Output did not convert to sparse time-value model"
Chris@649 488 << endl;
Chris@670 489 alignmentModel->setError
Chris@670 490 ("Output of program did not produce sparse time-value model");
Chris@683 491 delete csvOutput;
Chris@423 492 goto done;
Chris@595 493 }
Chris@683 494
Chris@649 495 if (path->isEmpty()) {
Chris@649 496 SVCERR << "ERROR: Align::alignmentProgramFinished: Output contained no mappings"
Chris@649 497 << endl;
Chris@670 498 alignmentModel->setError
Chris@670 499 ("Output of alignment program contained no mappings");
Chris@683 500 delete path;
Chris@423 501 goto done;
Chris@595 502 }
Chris@420 503
Chris@649 504 SVCERR << "Align::alignmentProgramFinished: Setting alignment path ("
Chris@650 505 << path->getEventCount() << " point(s))" << endl;
Chris@650 506
Chris@683 507 ModelById::add(std::shared_ptr<SparseTimeValueModel>(path));
Chris@683 508 alignmentModel->setPathFrom(path->getId());
Chris@420 509
Chris@683 510 emit alignmentComplete(alignmentModelId);
Chris@428 511
Chris@420 512 } else {
Chris@649 513 SVCERR << "ERROR: Align::alignmentProgramFinished: Aligner program "
Chris@649 514 << "failed: exit code " << exitCode << ", status " << status
Chris@649 515 << endl;
Chris@670 516 alignmentModel->setError
Chris@670 517 ("Aligner process returned non-zero exit status");
Chris@420 518 }
Chris@420 519
Chris@423 520 done:
Chris@670 521 m_pendingProcesses.erase(process);
Chris@423 522 delete process;
Chris@420 523 }
Chris@420 524