annotate framework/Align.cpp @ 701:e3dc68cc07f9

Some messing with parameters
author Chris Cannam
date Thu, 08 Aug 2019 13:34:12 +0100
parents 46ab91e26bfe
children e4d92aaa689c
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@687 92 Align::alignModelViaTransform(Document *doc,
Chris@687 93 ModelId referenceId,
Chris@687 94 ModelId otherId,
Chris@670 95 QString &error)
Chris@420 96 {
Chris@670 97 QMutexLocker locker (&m_mutex);
Chris@420 98
Chris@687 99 auto reference =
Chris@687 100 ModelById::getAs<RangeSummarisableTimeValueModel>(referenceId);
Chris@687 101 auto other =
Chris@687 102 ModelById::getAs<RangeSummarisableTimeValueModel>(otherId);
Chris@687 103
Chris@687 104 if (!reference || !other) return false;
Chris@420 105
Chris@691 106 // This involves creating a number of new models:
Chris@672 107 //
Chris@420 108 // 1. an AggregateWaveModel to provide the mixdowns of the main
Chris@420 109 // model and the new model in its two channels, as input to the
Chris@691 110 // MATCH plugin. We just call this one aggregateModel
Chris@672 111 //
Chris@670 112 // 2a. a SparseTimeValueModel which will be automatically created
Chris@670 113 // by FeatureExtractionModelTransformer when running the
Chris@670 114 // TuningDifference plugin to receive the relative tuning of the
Chris@670 115 // second model (if pitch-aware alignment is enabled in the
Chris@691 116 // preferences). We call this tuningDiffOutputModel.
Chris@672 117 //
Chris@670 118 // 2b. a SparseTimeValueModel which will be automatically created
Chris@670 119 // by FeatureExtractionPluginTransformer when running the MATCH
Chris@691 120 // plugin to perform alignment (so containing the alignment path).
Chris@691 121 // We call this one pathOutputModel.
Chris@672 122 //
Chris@691 123 // 2c. a SparseTimeValueModel used solely to provide faked
Chris@691 124 // completion information to the AlignmentModel while a
Chris@691 125 // TuningDifference calculation is going on. We call this
Chris@691 126 // preparatoryModel.
Chris@672 127 //
Chris@691 128 // 3. an AlignmentModel, which stores the path and carries out
Chris@691 129 // alignment lookups on it. We just call this one alignmentModel.
Chris@672 130 //
Chris@691 131 // Models 1 and 3 are registered with the document, which will
Chris@691 132 // eventually release them. We don't release them here except in
Chris@691 133 // the case where an activity fails before the point where we
Chris@691 134 // would otherwise have registered them with the document.
Chris@691 135 //
Chris@691 136 // Models 2a (tuningDiffOutputModel), 2b (pathOutputModel) and 2c
Chris@691 137 // (preparatoryModel) are not registered with the document. Model
Chris@691 138 // 2b (pathOutputModel) is not registered because we do not have a
Chris@691 139 // stable reference to the document at the point where it is
Chris@691 140 // created. Model 2c (preparatoryModel) is not registered because
Chris@691 141 // it is a bodge that we are embarrassed about, so we try to
Chris@691 142 // manage it ourselves without anyone else noticing. Model 2a is
Chris@691 143 // not registered for symmetry with the other two. These have to
Chris@691 144 // be released by us when finished with, but their lifespans do
Chris@691 145 // not extend beyond the end of the alignment procedure, so this
Chris@691 146 // should be ok.
Chris@420 147
Chris@420 148 AggregateWaveModel::ChannelSpecList components;
Chris@420 149
Chris@687 150 components.push_back
Chris@687 151 (AggregateWaveModel::ModelChannelSpec(referenceId, -1));
Chris@420 152
Chris@687 153 components.push_back
Chris@687 154 (AggregateWaveModel::ModelChannelSpec(otherId, -1));
Chris@420 155
Chris@683 156 auto aggregateModel = std::make_shared<AggregateWaveModel>(components);
Chris@687 157 auto aggregateModelId = ModelById::add(aggregateModel);
Chris@691 158 doc->addNonDerivedModel(aggregateModelId);
Chris@670 159
Chris@687 160 auto alignmentModel = std::make_shared<AlignmentModel>
Chris@687 161 (referenceId, otherId, ModelId());
Chris@687 162 auto alignmentModelId = ModelById::add(alignmentModel);
Chris@670 163
Chris@670 164 TransformId tdId = getTuningDifferenceTransformName();
Chris@670 165
Chris@670 166 if (tdId == "") {
Chris@670 167
Chris@687 168 if (beginTransformDrivenAlignment(aggregateModelId,
Chris@687 169 alignmentModelId)) {
Chris@687 170 other->setAlignment(alignmentModelId);
Chris@691 171 doc->addNonDerivedModel(alignmentModelId);
Chris@670 172 } else {
Chris@670 173 error = alignmentModel->getError();
Chris@683 174 ModelById::release(alignmentModel);
Chris@670 175 return false;
Chris@670 176 }
Chris@670 177
Chris@670 178 } else {
Chris@670 179
Chris@670 180 // Have a tuning-difference transform id, so run it
Chris@670 181 // asynchronously first
Chris@670 182
Chris@670 183 TransformFactory *tf = TransformFactory::getInstance();
Chris@670 184
Chris@670 185 Transform transform = tf->getDefaultTransformFor
Chris@670 186 (tdId, aggregateModel->getSampleRate());
Chris@670 187
Chris@678 188 transform.setParameter("maxduration", 60);
Chris@678 189 transform.setParameter("maxrange", 6);
Chris@678 190 transform.setParameter("finetuning", false);
Chris@671 191
Chris@670 192 SVDEBUG << "Align::alignModel: Tuning difference transform step size " << transform.getStepSize() << ", block size " << transform.getBlockSize() << endl;
Chris@670 193
Chris@670 194 ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance();
Chris@670 195
Chris@670 196 QString message;
Chris@691 197 ModelId tuningDiffOutputModelId = mtf->transform(transform,
Chris@691 198 aggregateModelId,
Chris@691 199 message);
Chris@670 200
Chris@691 201 auto tuningDiffOutputModel =
Chris@691 202 ModelById::getAs<SparseTimeValueModel>(tuningDiffOutputModelId);
Chris@691 203 if (!tuningDiffOutputModel) {
Chris@670 204 SVCERR << "Align::alignModel: ERROR: Failed to create tuning-difference output model (no Tuning Difference plugin?)" << endl;
Chris@670 205 error = message;
Chris@691 206 ModelById::release(alignmentModel);
Chris@670 207 return false;
Chris@670 208 }
Chris@670 209
Chris@687 210 other->setAlignment(alignmentModelId);
Chris@691 211 doc->addNonDerivedModel(alignmentModelId);
Chris@665 212
Chris@691 213 connect(tuningDiffOutputModel.get(),
Chris@691 214 SIGNAL(completionChanged(ModelId)),
Chris@687 215 this, SLOT(tuningDifferenceCompletionChanged(ModelId)));
Chris@420 216
Chris@671 217 TuningDiffRec rec;
Chris@687 218 rec.input = aggregateModelId;
Chris@687 219 rec.alignment = alignmentModelId;
Chris@677 220
Chris@671 221 // This model exists only so that the AlignmentModel can get a
Chris@671 222 // completion value from somewhere while the tuning difference
Chris@671 223 // calculation is going on
Chris@683 224 auto preparatoryModel = std::make_shared<SparseTimeValueModel>
Chris@683 225 (aggregateModel->getSampleRate(), 1);
Chris@687 226 auto preparatoryModelId = ModelById::add(preparatoryModel);
Chris@683 227 preparatoryModel->setCompletion(0);
Chris@687 228 rec.preparatory = preparatoryModelId;
Chris@671 229 alignmentModel->setPathFrom(rec.preparatory);
Chris@671 230
Chris@691 231 m_pendingTuningDiffs[tuningDiffOutputModelId] = rec;
Chris@698 232
Chris@698 233 SVDEBUG << "Align::alignModelViaTransform: Made a note of pending tuning diff output model id " << tuningDiffOutputModelId << " with input " << rec.input << ", alignment model " << rec.alignment << ", preparatory model " << rec.preparatory << endl;
Chris@670 234 }
Chris@670 235
Chris@670 236 return true;
Chris@670 237 }
Chris@670 238
Chris@671 239 void
Chris@691 240 Align::tuningDifferenceCompletionChanged(ModelId tuningDiffOutputModelId)
Chris@671 241 {
Chris@687 242 QMutexLocker locker(&m_mutex);
Chris@671 243
Chris@691 244 if (m_pendingTuningDiffs.find(tuningDiffOutputModelId) ==
Chris@691 245 m_pendingTuningDiffs.end()) {
Chris@698 246 SVDEBUG << "NOTE: Align::tuningDifferenceCompletionChanged: Model "
Chris@698 247 << tuningDiffOutputModelId
Chris@698 248 << " not found in pending tuning diff map, probably "
Chris@698 249 << "completed already" << endl;
Chris@683 250 return;
Chris@683 251 }
Chris@671 252
Chris@691 253 auto tuningDiffOutputModel =
Chris@691 254 ModelById::getAs<SparseTimeValueModel>(tuningDiffOutputModelId);
Chris@691 255 if (!tuningDiffOutputModel) {
Chris@683 256 SVCERR << "WARNING: Align::tuningDifferenceCompletionChanged: Model "
Chris@691 257 << tuningDiffOutputModelId
Chris@691 258 << " not known as SparseTimeValueModel" << endl;
Chris@683 259 return;
Chris@683 260 }
Chris@683 261
Chris@691 262 TuningDiffRec rec = m_pendingTuningDiffs[tuningDiffOutputModelId];
Chris@683 263
Chris@691 264 auto alignmentModel = ModelById::getAs<AlignmentModel>(rec.alignment);
Chris@691 265 if (!alignmentModel) {
Chris@683 266 SVCERR << "WARNING: Align::tuningDifferenceCompletionChanged:"
Chris@683 267 << "alignment model has disappeared" << endl;
Chris@683 268 return;
Chris@683 269 }
Chris@683 270
Chris@671 271 int completion = 0;
Chris@691 272 bool done = tuningDiffOutputModel->isReady(&completion);
Chris@671 273
Chris@671 274 if (!done) {
Chris@671 275 // This will be the completion the alignment model reports,
Chris@671 276 // before the alignment actually begins. It goes up from 0 to
Chris@671 277 // 99 (not 100!) and then back to 0 again when we start
Chris@671 278 // calculating the actual path in the following phase
Chris@671 279 int clamped = (completion == 100 ? 99 : completion);
Chris@691 280 auto preparatoryModel =
Chris@691 281 ModelById::getAs<SparseTimeValueModel>(rec.preparatory);
Chris@691 282 if (preparatoryModel) {
Chris@691 283 preparatoryModel->setCompletion(clamped);
Chris@683 284 }
Chris@671 285 return;
Chris@671 286 }
Chris@671 287
Chris@671 288 float tuningFrequency = 440.f;
Chris@671 289
Chris@691 290 if (!tuningDiffOutputModel->isEmpty()) {
Chris@691 291 tuningFrequency = tuningDiffOutputModel->getAllEvents()[0].getValue();
Chris@671 292 SVCERR << "Align::tuningDifferenceCompletionChanged: Reported tuning frequency = " << tuningFrequency << endl;
Chris@671 293 } else {
Chris@671 294 SVCERR << "Align::tuningDifferenceCompletionChanged: No tuning frequency reported" << endl;
Chris@671 295 }
Chris@698 296
Chris@691 297 ModelById::release(tuningDiffOutputModel);
Chris@683 298
Chris@691 299 alignmentModel->setPathFrom({}); // replace preparatoryModel
Chris@691 300 ModelById::release(rec.preparatory);
Chris@691 301 rec.preparatory = {};
Chris@691 302
Chris@691 303 m_pendingTuningDiffs.erase(tuningDiffOutputModelId);
Chris@698 304
Chris@698 305 SVDEBUG << "Align::tuningDifferenceCompletionChanged: Erasing model "
Chris@698 306 << tuningDiffOutputModelId << " from pending tuning diffs and "
Chris@698 307 << "launching the alignment phase for alignment model "
Chris@698 308 << rec.alignment << " with tuning frequency "
Chris@698 309 << tuningFrequency << endl;
Chris@671 310
Chris@671 311 beginTransformDrivenAlignment
Chris@671 312 (rec.input, rec.alignment, tuningFrequency);
Chris@671 313 }
Chris@671 314
Chris@670 315 bool
Chris@683 316 Align::beginTransformDrivenAlignment(ModelId aggregateModelId,
Chris@683 317 ModelId alignmentModelId,
Chris@670 318 float tuningFrequency)
Chris@670 319 {
Chris@428 320 TransformId id = getAlignmentTransformName();
Chris@420 321
Chris@420 322 TransformFactory *tf = TransformFactory::getInstance();
Chris@420 323
Chris@691 324 auto aggregateModel =
Chris@691 325 ModelById::getAs<AggregateWaveModel>(aggregateModelId);
Chris@691 326 auto alignmentModel =
Chris@691 327 ModelById::getAs<AlignmentModel>(alignmentModelId);
Chris@683 328
Chris@683 329 if (!aggregateModel || !alignmentModel) {
Chris@683 330 SVCERR << "Align::alignModel: ERROR: One or other of the aggregate & alignment models has disappeared" << endl;
Chris@683 331 return false;
Chris@683 332 }
Chris@683 333
Chris@420 334 Transform transform = tf->getDefaultTransformFor
Chris@420 335 (id, aggregateModel->getSampleRate());
Chris@420 336
Chris@420 337 transform.setStepSize(transform.getBlockSize()/2);
Chris@420 338 transform.setParameter("serialise", 1);
Chris@420 339 transform.setParameter("smooth", 0);
Chris@699 340 transform.setParameter("zonewidth", 40);
Chris@701 341 // transform.setParameter("noise", true);
Chris@699 342 transform.setParameter("minfreq", 250);
Chris@701 343 // transform.setParameter("usechroma", 1);
Chris@420 344
Chris@670 345 if (tuningFrequency != 0.f) {
Chris@670 346 transform.setParameter("freq2", tuningFrequency);
Chris@670 347 }
Chris@670 348
Chris@420 349 SVDEBUG << "Align::alignModel: Alignment transform step size " << transform.getStepSize() << ", block size " << transform.getBlockSize() << endl;
Chris@420 350
Chris@420 351 ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance();
Chris@420 352
Chris@420 353 QString message;
Chris@691 354 ModelId pathOutputModelId = mtf->transform
Chris@683 355 (transform, aggregateModelId, message);
Chris@420 356
Chris@691 357 if (pathOutputModelId.isNone()) {
Chris@420 358 transform.setStepSize(0);
Chris@691 359 pathOutputModelId = mtf->transform
Chris@683 360 (transform, aggregateModelId, message);
Chris@420 361 }
Chris@420 362
Chris@691 363 auto pathOutputModel =
Chris@691 364 ModelById::getAs<SparseTimeValueModel>(pathOutputModelId);
Chris@420 365
Chris@670 366 //!!! callers will need to be updated to get error from
Chris@670 367 //!!! alignment model after initial call
Chris@670 368
Chris@691 369 if (!pathOutputModel) {
Chris@649 370 SVCERR << "Align::alignModel: ERROR: Failed to create alignment path (no MATCH plugin?)" << endl;
Chris@670 371 alignmentModel->setError(message);
Chris@420 372 return false;
Chris@420 373 }
Chris@420 374
Chris@691 375 pathOutputModel->setCompletion(0);
Chris@691 376 alignmentModel->setPathFrom(pathOutputModelId);
Chris@691 377
Chris@691 378 m_pendingAlignments[alignmentModelId] = pathOutputModelId;
Chris@420 379
Chris@687 380 connect(alignmentModel.get(), SIGNAL(completionChanged(ModelId)),
Chris@687 381 this, SLOT(alignmentCompletionChanged(ModelId)));
Chris@420 382
Chris@420 383 return true;
Chris@420 384 }
Chris@420 385
Chris@428 386 void
Chris@691 387 Align::alignmentCompletionChanged(ModelId alignmentModelId)
Chris@428 388 {
Chris@670 389 QMutexLocker locker (&m_mutex);
Chris@683 390
Chris@691 391 auto alignmentModel = ModelById::getAs<AlignmentModel>(alignmentModelId);
Chris@691 392
Chris@691 393 if (alignmentModel && alignmentModel->isReady()) {
Chris@691 394
Chris@691 395 if (m_pendingAlignments.find(alignmentModelId) !=
Chris@691 396 m_pendingAlignments.end()) {
Chris@691 397 ModelId pathOutputModelId = m_pendingAlignments[alignmentModelId];
Chris@691 398 ModelById::release(pathOutputModelId);
Chris@691 399 m_pendingAlignments.erase(alignmentModelId);
Chris@691 400 }
Chris@691 401
Chris@691 402 disconnect(alignmentModel.get(),
Chris@691 403 SIGNAL(completionChanged(ModelId)),
Chris@687 404 this, SLOT(alignmentCompletionChanged(ModelId)));
Chris@691 405 emit alignmentComplete(alignmentModelId);
Chris@428 406 }
Chris@428 407 }
Chris@428 408
Chris@420 409 bool
Chris@691 410 Align::alignModelViaProgram(Document *doc,
Chris@687 411 ModelId referenceId,
Chris@687 412 ModelId otherId,
Chris@687 413 QString program,
Chris@687 414 QString &error)
Chris@420 415 {
Chris@670 416 QMutexLocker locker (&m_mutex);
Chris@430 417
Chris@420 418 // Run an external program, passing to it paths to the main
Chris@420 419 // model's audio file and the new model's audio file. It returns
Chris@420 420 // the path in CSV form through stdout.
Chris@420 421
Chris@687 422 auto reference = ModelById::getAs<ReadOnlyWaveFileModel>(referenceId);
Chris@687 423 auto other = ModelById::getAs<ReadOnlyWaveFileModel>(otherId);
Chris@687 424 if (!reference || !other) {
Chris@649 425 SVCERR << "ERROR: Align::alignModelViaProgram: Can't align non-read-only models via program (no local filename available)" << endl;
Chris@515 426 return false;
Chris@515 427 }
Chris@687 428
Chris@687 429 while (!reference->isReady(nullptr) || !other->isReady(nullptr)) {
Chris@687 430 qApp->processEvents();
Chris@687 431 }
Chris@515 432
Chris@687 433 QString refPath = reference->getLocalFilename();
Chris@687 434 QString otherPath = other->getLocalFilename();
Chris@420 435
Chris@420 436 if (refPath == "" || otherPath == "") {
Chris@670 437 error = "Failed to find local filepath for wave-file model";
Chris@595 438 return false;
Chris@420 439 }
Chris@420 440
Chris@687 441 auto alignmentModel =
Chris@687 442 std::make_shared<AlignmentModel>(referenceId, otherId, ModelId());
Chris@687 443 auto alignmentModelId = ModelById::add(alignmentModel);
Chris@687 444 other->setAlignment(alignmentModelId);
Chris@423 445
Chris@423 446 QProcess *process = new QProcess;
Chris@420 447 QStringList args;
Chris@420 448 args << refPath << otherPath;
Chris@423 449
Chris@423 450 connect(process, SIGNAL(finished(int, QProcess::ExitStatus)),
Chris@423 451 this, SLOT(alignmentProgramFinished(int, QProcess::ExitStatus)));
Chris@420 452
Chris@687 453 m_pendingProcesses[process] = alignmentModelId;
Chris@423 454 process->start(program, args);
Chris@420 455
Chris@423 456 bool success = process->waitForStarted();
Chris@423 457
Chris@423 458 if (!success) {
Chris@649 459 SVCERR << "ERROR: Align::alignModelViaProgram: Program did not start"
Chris@649 460 << endl;
Chris@670 461 error = "Alignment program could not be started";
Chris@670 462 m_pendingProcesses.erase(process);
Chris@687 463 other->setAlignment({});
Chris@691 464 ModelById::release(alignmentModelId);
Chris@423 465 delete process;
Chris@423 466 }
Chris@423 467
Chris@691 468 doc->addNonDerivedModel(alignmentModelId);
Chris@423 469 return success;
Chris@423 470 }
Chris@423 471
Chris@423 472 void
Chris@423 473 Align::alignmentProgramFinished(int exitCode, QProcess::ExitStatus status)
Chris@423 474 {
Chris@670 475 QMutexLocker locker (&m_mutex);
Chris@670 476
Chris@649 477 SVCERR << "Align::alignmentProgramFinished" << endl;
Chris@423 478
Chris@423 479 QProcess *process = qobject_cast<QProcess *>(sender());
Chris@423 480
Chris@670 481 if (m_pendingProcesses.find(process) == m_pendingProcesses.end()) {
Chris@649 482 SVCERR << "ERROR: Align::alignmentProgramFinished: Process " << process
Chris@649 483 << " not found in process model map!" << endl;
Chris@423 484 return;
Chris@423 485 }
Chris@423 486
Chris@683 487 ModelId alignmentModelId = m_pendingProcesses[process];
Chris@683 488 auto alignmentModel = ModelById::getAs<AlignmentModel>(alignmentModelId);
Chris@683 489 if (!alignmentModel) return;
Chris@423 490
Chris@423 491 if (exitCode == 0 && status == 0) {
Chris@420 492
Chris@595 493 CSVFormat format;
Chris@595 494 format.setModelType(CSVFormat::TwoDimensionalModel);
Chris@595 495 format.setTimingType(CSVFormat::ExplicitTiming);
Chris@595 496 format.setTimeUnits(CSVFormat::TimeSeconds);
Chris@595 497 format.setColumnCount(2);
Chris@425 498 // The output format has time in the reference file first, and
Chris@425 499 // time in the "other" file in the second column. This is a
Chris@425 500 // more natural approach for a command-line alignment tool,
Chris@425 501 // but it's the opposite of what we expect for native
Chris@425 502 // alignment paths, which map from "other" file to
Chris@425 503 // reference. These column purpose settings reflect that.
Chris@595 504 format.setColumnPurpose(1, CSVFormat::ColumnStartTime);
Chris@595 505 format.setColumnPurpose(0, CSVFormat::ColumnValue);
Chris@595 506 format.setAllowQuoting(false);
Chris@595 507 format.setSeparator(',');
Chris@420 508
Chris@595 509 CSVFileReader reader(process, format, alignmentModel->getSampleRate());
Chris@595 510 if (!reader.isOK()) {
Chris@649 511 SVCERR << "ERROR: Align::alignmentProgramFinished: Failed to parse output"
Chris@649 512 << endl;
Chris@670 513 alignmentModel->setError
Chris@670 514 (QString("Failed to parse output of program: %1")
Chris@670 515 .arg(reader.getError()));
Chris@423 516 goto done;
Chris@595 517 }
Chris@420 518
Chris@683 519 //!!! to use ById?
Chris@683 520
Chris@595 521 Model *csvOutput = reader.load();
Chris@420 522
Chris@687 523 SparseTimeValueModel *path =
Chris@687 524 qobject_cast<SparseTimeValueModel *>(csvOutput);
Chris@595 525 if (!path) {
Chris@649 526 SVCERR << "ERROR: Align::alignmentProgramFinished: Output did not convert to sparse time-value model"
Chris@649 527 << endl;
Chris@670 528 alignmentModel->setError
Chris@670 529 ("Output of program did not produce sparse time-value model");
Chris@683 530 delete csvOutput;
Chris@423 531 goto done;
Chris@595 532 }
Chris@683 533
Chris@649 534 if (path->isEmpty()) {
Chris@649 535 SVCERR << "ERROR: Align::alignmentProgramFinished: Output contained no mappings"
Chris@649 536 << endl;
Chris@670 537 alignmentModel->setError
Chris@670 538 ("Output of alignment program contained no mappings");
Chris@683 539 delete path;
Chris@423 540 goto done;
Chris@595 541 }
Chris@420 542
Chris@649 543 SVCERR << "Align::alignmentProgramFinished: Setting alignment path ("
Chris@650 544 << path->getEventCount() << " point(s))" << endl;
Chris@650 545
Chris@687 546 auto pathId =
Chris@687 547 ModelById::add(std::shared_ptr<SparseTimeValueModel>(path));
Chris@687 548 alignmentModel->setPathFrom(pathId);
Chris@420 549
Chris@683 550 emit alignmentComplete(alignmentModelId);
Chris@687 551
Chris@687 552 ModelById::release(pathId);
Chris@428 553
Chris@420 554 } else {
Chris@649 555 SVCERR << "ERROR: Align::alignmentProgramFinished: Aligner program "
Chris@649 556 << "failed: exit code " << exitCode << ", status " << status
Chris@649 557 << endl;
Chris@670 558 alignmentModel->setError
Chris@670 559 ("Aligner process returned non-zero exit status");
Chris@420 560 }
Chris@420 561
Chris@423 562 done:
Chris@670 563 m_pendingProcesses.erase(process);
Chris@423 564 delete process;
Chris@420 565 }
Chris@420 566