annotate framework/Align.cpp @ 681:c7406ebcd51c by-id

Update for ModelById
author Chris Cannam
date Mon, 24 Jun 2019 16:14:12 +0100
parents 16c1077da62c
children 0736beb8b852
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@670 36 Align::alignModel(Document *doc, Model *ref, Model *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@670 92 Align::alignModelViaTransform(Document *doc, Model *ref, Model *other,
Chris@670 93 QString &error)
Chris@420 94 {
Chris@670 95 QMutexLocker locker (&m_mutex);
Chris@670 96
Chris@420 97 RangeSummarisableTimeValueModel *reference = qobject_cast
Chris@420 98 <RangeSummarisableTimeValueModel *>(ref);
Chris@420 99
Chris@420 100 RangeSummarisableTimeValueModel *rm = qobject_cast
Chris@420 101 <RangeSummarisableTimeValueModel *>(other);
Chris@420 102
Chris@420 103 if (!reference || !rm) return false; // but this should have been tested already
Chris@420 104
Chris@670 105 // This involves creating either three or four new models:
Chris@672 106 //
Chris@420 107 // 1. an AggregateWaveModel to provide the mixdowns of the main
Chris@420 108 // model and the new model in its two channels, as input to the
Chris@420 109 // MATCH plugin
Chris@672 110 //
Chris@670 111 // 2a. a SparseTimeValueModel which will be automatically created
Chris@670 112 // by FeatureExtractionModelTransformer when running the
Chris@670 113 // TuningDifference plugin to receive the relative tuning of the
Chris@670 114 // second model (if pitch-aware alignment is enabled in the
Chris@670 115 // preferences)
Chris@672 116 //
Chris@670 117 // 2b. a SparseTimeValueModel which will be automatically created
Chris@670 118 // by FeatureExtractionPluginTransformer when running the MATCH
Chris@670 119 // plugin to perform alignment (so containing the alignment path)
Chris@672 120 //
Chris@420 121 // 3. an AlignmentModel, which stores the path model and carries
Chris@420 122 // out alignment lookups on it.
Chris@672 123 //
Chris@670 124 // The AggregateWaveModel [1] is registered with the document,
Chris@670 125 // which deletes it when it is invalidated (when one of its
Chris@670 126 // components is deleted). The SparseTimeValueModel [2a] is reused
Chris@670 127 // by us when starting the alignment process proper, and is then
Chris@670 128 // deleted by us. The SparseTimeValueModel [2b] is passed to the
Chris@670 129 // AlignmentModel, which takes ownership of it. The AlignmentModel
Chris@670 130 // is attached to the new model we are aligning, which also takes
Chris@670 131 // ownership of it. The only one of these models that we need to
Chris@670 132 // delete here is the SparseTimeValueModel [2a].
Chris@672 133 //
Chris@672 134 // (We also create a sneaky additional SparseTimeValueModel
Chris@672 135 // temporarily so we can attach completion information to it -
Chris@672 136 // this is quite unnecessary from the perspective of simply
Chris@672 137 // producing the results.)
Chris@420 138
Chris@420 139 AggregateWaveModel::ChannelSpecList components;
Chris@420 140
Chris@420 141 components.push_back(AggregateWaveModel::ModelChannelSpec
Chris@681 142 (reference->getId(), -1));
Chris@420 143
Chris@420 144 components.push_back(AggregateWaveModel::ModelChannelSpec
Chris@681 145 (rm->getId(), -1));
Chris@420 146
Chris@665 147 AggregateWaveModel *aggregateModel = new AggregateWaveModel(components);
Chris@665 148 doc->addAggregateModel(aggregateModel);
Chris@670 149
Chris@670 150 AlignmentModel *alignmentModel =
Chris@670 151 new AlignmentModel(reference, other, nullptr);
Chris@670 152
Chris@670 153 TransformId tdId = getTuningDifferenceTransformName();
Chris@670 154
Chris@670 155 if (tdId == "") {
Chris@670 156
Chris@670 157 if (beginTransformDrivenAlignment(aggregateModel, alignmentModel)) {
Chris@670 158 rm->setAlignment(alignmentModel);
Chris@670 159 } else {
Chris@670 160 error = alignmentModel->getError();
Chris@670 161 delete 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@670 184 Model *transformOutput = mtf->transform(transform, aggregateModel, message);
Chris@670 185
Chris@670 186 SparseTimeValueModel *tdout = dynamic_cast<SparseTimeValueModel *>
Chris@670 187 (transformOutput);
Chris@670 188
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 delete tdout;
Chris@670 192 error = message;
Chris@670 193 return false;
Chris@670 194 }
Chris@670 195
Chris@670 196 rm->setAlignment(alignmentModel);
Chris@665 197
Chris@670 198 connect(tdout, SIGNAL(completionChanged()),
Chris@670 199 this, SLOT(tuningDifferenceCompletionChanged()));
Chris@420 200
Chris@671 201 TuningDiffRec rec;
Chris@671 202 rec.input = aggregateModel;
Chris@671 203 rec.alignment = alignmentModel;
Chris@671 204
Chris@677 205 connect(aggregateModel, SIGNAL(aboutToBeDeleted()),
Chris@677 206 this, SLOT(aggregateModelAboutToBeDeleted()));
Chris@677 207
Chris@671 208 // This model exists only so that the AlignmentModel can get a
Chris@671 209 // completion value from somewhere while the tuning difference
Chris@671 210 // calculation is going on
Chris@671 211 rec.preparatory = new SparseTimeValueModel
Chris@671 212 (aggregateModel->getSampleRate(), 1);;
Chris@671 213 rec.preparatory->setCompletion(0);
Chris@671 214 alignmentModel->setPathFrom(rec.preparatory);
Chris@671 215
Chris@671 216 m_pendingTuningDiffs[tdout] = rec;
Chris@670 217 }
Chris@670 218
Chris@670 219 return true;
Chris@670 220 }
Chris@670 221
Chris@671 222 void
Chris@677 223 Align::aggregateModelAboutToBeDeleted()
Chris@677 224 {
Chris@677 225 SVCERR << "Align::aggregateModelAboutToBeDeleted" << endl;
Chris@677 226
Chris@677 227 QObject *s = sender();
Chris@677 228 AggregateWaveModel *awm = qobject_cast<AggregateWaveModel *>(s);
Chris@677 229 if (!awm) return;
Chris@677 230 QMutexLocker locker(&m_mutex);
Chris@677 231
Chris@677 232 SVCERR << "Align::aggregateModelAboutToBeDeleted: awm = " << awm
Chris@677 233 << endl;
Chris@677 234
Chris@677 235 for (const auto &p : m_pendingTuningDiffs) {
Chris@677 236 if (p.second.input == awm) {
Chris@677 237 SVCERR << "we have a record of this, getting rid of it" << endl;
Chris@677 238 m_pendingTuningDiffs.erase(p.first);
Chris@677 239 return;
Chris@677 240 }
Chris@677 241 }
Chris@677 242 }
Chris@677 243
Chris@677 244 void
Chris@671 245 Align::tuningDifferenceCompletionChanged()
Chris@671 246 {
Chris@671 247 QMutexLocker locker (&m_mutex);
Chris@671 248
Chris@671 249 SparseTimeValueModel *td = qobject_cast<SparseTimeValueModel *>(sender());
Chris@671 250 if (!td) return;
Chris@671 251
Chris@671 252 if (m_pendingTuningDiffs.find(td) == m_pendingTuningDiffs.end()) {
Chris@671 253 SVCERR << "ERROR: Align::tuningDifferenceCompletionChanged: Model "
Chris@671 254 << td << " not found in pending tuning diff map!" << endl;
Chris@671 255 return;
Chris@671 256 }
Chris@671 257
Chris@671 258 TuningDiffRec rec = m_pendingTuningDiffs[td];
Chris@671 259
Chris@671 260 int completion = 0;
Chris@671 261 bool done = td->isReady(&completion);
Chris@671 262
Chris@674 263 // SVCERR << "Align::tuningDifferenceCompletionChanged: done = " << done << ", completion = " << completion << endl;
Chris@671 264
Chris@671 265 if (!done) {
Chris@671 266 // This will be the completion the alignment model reports,
Chris@671 267 // before the alignment actually begins. It goes up from 0 to
Chris@671 268 // 99 (not 100!) and then back to 0 again when we start
Chris@671 269 // calculating the actual path in the following phase
Chris@671 270 int clamped = (completion == 100 ? 99 : completion);
Chris@674 271 // SVCERR << "Align::tuningDifferenceCompletionChanged: setting rec.preparatory completion to " << clamped << endl;
Chris@671 272 rec.preparatory->setCompletion(clamped);
Chris@671 273 return;
Chris@671 274 }
Chris@671 275
Chris@671 276 float tuningFrequency = 440.f;
Chris@671 277
Chris@671 278 if (!td->isEmpty()) {
Chris@671 279 tuningFrequency = td->getAllEvents()[0].getValue();
Chris@671 280 SVCERR << "Align::tuningDifferenceCompletionChanged: Reported tuning frequency = " << tuningFrequency << endl;
Chris@671 281 } else {
Chris@671 282 SVCERR << "Align::tuningDifferenceCompletionChanged: No tuning frequency reported" << endl;
Chris@671 283 }
Chris@671 284
Chris@671 285 m_pendingTuningDiffs.erase(td);
Chris@671 286 td->aboutToDelete();
Chris@671 287 delete td;
Chris@671 288
Chris@671 289 rec.alignment->setPathFrom(nullptr);
Chris@671 290
Chris@671 291 beginTransformDrivenAlignment
Chris@671 292 (rec.input, rec.alignment, tuningFrequency);
Chris@671 293 }
Chris@671 294
Chris@670 295 bool
Chris@670 296 Align::beginTransformDrivenAlignment(AggregateWaveModel *aggregateModel,
Chris@670 297 AlignmentModel *alignmentModel,
Chris@670 298 float tuningFrequency)
Chris@670 299 {
Chris@428 300 TransformId id = getAlignmentTransformName();
Chris@420 301
Chris@420 302 TransformFactory *tf = TransformFactory::getInstance();
Chris@420 303
Chris@420 304 Transform transform = tf->getDefaultTransformFor
Chris@420 305 (id, aggregateModel->getSampleRate());
Chris@420 306
Chris@420 307 transform.setStepSize(transform.getBlockSize()/2);
Chris@420 308 transform.setParameter("serialise", 1);
Chris@420 309 transform.setParameter("smooth", 0);
Chris@420 310
Chris@670 311 if (tuningFrequency != 0.f) {
Chris@670 312 transform.setParameter("freq2", tuningFrequency);
Chris@670 313 }
Chris@670 314
Chris@420 315 SVDEBUG << "Align::alignModel: Alignment transform step size " << transform.getStepSize() << ", block size " << transform.getBlockSize() << endl;
Chris@420 316
Chris@420 317 ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance();
Chris@420 318
Chris@420 319 QString message;
Chris@670 320 Model *transformOutput = mtf->transform
Chris@670 321 (transform, aggregateModel, message);
Chris@420 322
Chris@420 323 if (!transformOutput) {
Chris@420 324 transform.setStepSize(0);
Chris@670 325 transformOutput = mtf->transform
Chris@670 326 (transform, aggregateModel, message);
Chris@420 327 }
Chris@420 328
Chris@420 329 SparseTimeValueModel *path = dynamic_cast<SparseTimeValueModel *>
Chris@420 330 (transformOutput);
Chris@420 331
Chris@670 332 //!!! callers will need to be updated to get error from
Chris@670 333 //!!! alignment model after initial call
Chris@670 334
Chris@420 335 if (!path) {
Chris@649 336 SVCERR << "Align::alignModel: ERROR: Failed to create alignment path (no MATCH plugin?)" << endl;
Chris@420 337 delete transformOutput;
Chris@670 338 alignmentModel->setError(message);
Chris@420 339 return false;
Chris@420 340 }
Chris@420 341
Chris@420 342 path->setCompletion(0);
Chris@670 343 alignmentModel->setPathFrom(path);
Chris@420 344
Chris@428 345 connect(alignmentModel, SIGNAL(completionChanged()),
Chris@428 346 this, SLOT(alignmentCompletionChanged()));
Chris@420 347
Chris@420 348 return true;
Chris@420 349 }
Chris@420 350
Chris@428 351 void
Chris@428 352 Align::alignmentCompletionChanged()
Chris@428 353 {
Chris@670 354 QMutexLocker locker (&m_mutex);
Chris@670 355
Chris@428 356 AlignmentModel *am = qobject_cast<AlignmentModel *>(sender());
Chris@428 357 if (!am) return;
Chris@428 358 if (am->isReady()) {
Chris@428 359 disconnect(am, SIGNAL(completionChanged()),
Chris@428 360 this, SLOT(alignmentCompletionChanged()));
Chris@428 361 emit alignmentComplete(am);
Chris@428 362 }
Chris@428 363 }
Chris@428 364
Chris@420 365 bool
Chris@670 366 Align::alignModelViaProgram(Document *, Model *ref, Model *other,
Chris@670 367 QString program, QString &error)
Chris@420 368 {
Chris@670 369 QMutexLocker locker (&m_mutex);
Chris@670 370
Chris@420 371 WaveFileModel *reference = qobject_cast<WaveFileModel *>(ref);
Chris@420 372 WaveFileModel *rm = qobject_cast<WaveFileModel *>(other);
Chris@420 373
Chris@515 374 if (!reference || !rm) {
Chris@515 375 return false; // but this should have been tested already
Chris@515 376 }
Chris@420 377
Chris@636 378 while (!reference->isReady(nullptr) || !rm->isReady(nullptr)) {
Chris@430 379 qApp->processEvents();
Chris@430 380 }
Chris@430 381
Chris@420 382 // Run an external program, passing to it paths to the main
Chris@420 383 // model's audio file and the new model's audio file. It returns
Chris@420 384 // the path in CSV form through stdout.
Chris@420 385
Chris@515 386 ReadOnlyWaveFileModel *roref = qobject_cast<ReadOnlyWaveFileModel *>(reference);
Chris@515 387 ReadOnlyWaveFileModel *rorm = qobject_cast<ReadOnlyWaveFileModel *>(rm);
Chris@515 388 if (!roref || !rorm) {
Chris@649 389 SVCERR << "ERROR: Align::alignModelViaProgram: Can't align non-read-only models via program (no local filename available)" << endl;
Chris@515 390 return false;
Chris@515 391 }
Chris@515 392
Chris@515 393 QString refPath = roref->getLocalFilename();
Chris@515 394 QString otherPath = rorm->getLocalFilename();
Chris@420 395
Chris@420 396 if (refPath == "" || otherPath == "") {
Chris@670 397 error = "Failed to find local filepath for wave-file model";
Chris@595 398 return false;
Chris@420 399 }
Chris@420 400
Chris@665 401 AlignmentModel *alignmentModel =
Chris@665 402 new AlignmentModel(reference, other, nullptr);
Chris@423 403 rm->setAlignment(alignmentModel);
Chris@423 404
Chris@423 405 QProcess *process = new QProcess;
Chris@420 406 QStringList args;
Chris@420 407 args << refPath << otherPath;
Chris@423 408
Chris@423 409 connect(process, SIGNAL(finished(int, QProcess::ExitStatus)),
Chris@423 410 this, SLOT(alignmentProgramFinished(int, QProcess::ExitStatus)));
Chris@420 411
Chris@670 412 m_pendingProcesses[process] = alignmentModel;
Chris@423 413 process->start(program, args);
Chris@420 414
Chris@423 415 bool success = process->waitForStarted();
Chris@423 416
Chris@423 417 if (!success) {
Chris@649 418 SVCERR << "ERROR: Align::alignModelViaProgram: Program did not start"
Chris@649 419 << endl;
Chris@670 420 error = "Alignment program could not be started";
Chris@670 421 m_pendingProcesses.erase(process);
Chris@636 422 rm->setAlignment(nullptr); // deletes alignmentModel as well
Chris@423 423 delete process;
Chris@423 424 }
Chris@423 425
Chris@423 426 return success;
Chris@423 427 }
Chris@423 428
Chris@423 429 void
Chris@423 430 Align::alignmentProgramFinished(int exitCode, QProcess::ExitStatus status)
Chris@423 431 {
Chris@670 432 QMutexLocker locker (&m_mutex);
Chris@670 433
Chris@649 434 SVCERR << "Align::alignmentProgramFinished" << endl;
Chris@423 435
Chris@423 436 QProcess *process = qobject_cast<QProcess *>(sender());
Chris@423 437
Chris@670 438 if (m_pendingProcesses.find(process) == m_pendingProcesses.end()) {
Chris@649 439 SVCERR << "ERROR: Align::alignmentProgramFinished: Process " << process
Chris@649 440 << " not found in process model map!" << endl;
Chris@423 441 return;
Chris@423 442 }
Chris@423 443
Chris@670 444 AlignmentModel *alignmentModel = m_pendingProcesses[process];
Chris@423 445
Chris@423 446 if (exitCode == 0 && status == 0) {
Chris@420 447
Chris@595 448 CSVFormat format;
Chris@595 449 format.setModelType(CSVFormat::TwoDimensionalModel);
Chris@595 450 format.setTimingType(CSVFormat::ExplicitTiming);
Chris@595 451 format.setTimeUnits(CSVFormat::TimeSeconds);
Chris@595 452 format.setColumnCount(2);
Chris@425 453 // The output format has time in the reference file first, and
Chris@425 454 // time in the "other" file in the second column. This is a
Chris@425 455 // more natural approach for a command-line alignment tool,
Chris@425 456 // but it's the opposite of what we expect for native
Chris@425 457 // alignment paths, which map from "other" file to
Chris@425 458 // reference. These column purpose settings reflect that.
Chris@595 459 format.setColumnPurpose(1, CSVFormat::ColumnStartTime);
Chris@595 460 format.setColumnPurpose(0, CSVFormat::ColumnValue);
Chris@595 461 format.setAllowQuoting(false);
Chris@595 462 format.setSeparator(',');
Chris@420 463
Chris@595 464 CSVFileReader reader(process, format, alignmentModel->getSampleRate());
Chris@595 465 if (!reader.isOK()) {
Chris@649 466 SVCERR << "ERROR: Align::alignmentProgramFinished: Failed to parse output"
Chris@649 467 << endl;
Chris@670 468 alignmentModel->setError
Chris@670 469 (QString("Failed to parse output of program: %1")
Chris@670 470 .arg(reader.getError()));
Chris@423 471 goto done;
Chris@595 472 }
Chris@420 473
Chris@595 474 Model *csvOutput = reader.load();
Chris@420 475
Chris@595 476 SparseTimeValueModel *path = qobject_cast<SparseTimeValueModel *>(csvOutput);
Chris@595 477 if (!path) {
Chris@649 478 SVCERR << "ERROR: Align::alignmentProgramFinished: Output did not convert to sparse time-value model"
Chris@649 479 << endl;
Chris@670 480 alignmentModel->setError
Chris@670 481 ("Output of program did not produce sparse time-value model");
Chris@423 482 goto done;
Chris@595 483 }
Chris@420 484
Chris@649 485 if (path->isEmpty()) {
Chris@649 486 SVCERR << "ERROR: Align::alignmentProgramFinished: Output contained no mappings"
Chris@649 487 << endl;
Chris@670 488 alignmentModel->setError
Chris@670 489 ("Output of alignment program contained no mappings");
Chris@423 490 goto done;
Chris@595 491 }
Chris@420 492
Chris@649 493 SVCERR << "Align::alignmentProgramFinished: Setting alignment path ("
Chris@650 494 << path->getEventCount() << " point(s))" << endl;
Chris@650 495
Chris@423 496 alignmentModel->setPathFrom(path);
Chris@420 497
Chris@428 498 emit alignmentComplete(alignmentModel);
Chris@428 499
Chris@420 500 } else {
Chris@649 501 SVCERR << "ERROR: Align::alignmentProgramFinished: Aligner program "
Chris@649 502 << "failed: exit code " << exitCode << ", status " << status
Chris@649 503 << endl;
Chris@670 504 alignmentModel->setError
Chris@670 505 ("Aligner process returned non-zero exit status");
Chris@420 506 }
Chris@420 507
Chris@423 508 done:
Chris@670 509 m_pendingProcesses.erase(process);
Chris@423 510 delete process;
Chris@420 511 }
Chris@420 512