Mercurial > hg > svapp
comparison align/Align.cpp @ 744:36772d79cf44 pitch-align
Move Align to new align directory
author | Chris Cannam |
---|---|
date | Fri, 03 Apr 2020 10:17:46 +0100 |
parents | framework/Align.cpp@464fed3096f5 |
children | 31289e8592c7 |
comparison
equal
deleted
inserted
replaced
743:7b1d30af4b38 | 744:36772d79cf44 |
---|---|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ | |
2 | |
3 /* | |
4 Sonic Visualiser | |
5 An audio file viewer and annotation editor. | |
6 Centre for Digital Music, Queen Mary, University of London. | |
7 | |
8 This program is free software; you can redistribute it and/or | |
9 modify it under the terms of the GNU General Public License as | |
10 published by the Free Software Foundation; either version 2 of the | |
11 License, or (at your option) any later version. See the file | |
12 COPYING included with this distribution for more information. | |
13 */ | |
14 | |
15 #include "Align.h" | |
16 #include "framework/Document.h" | |
17 | |
18 #include "data/model/WaveFileModel.h" | |
19 #include "data/model/ReadOnlyWaveFileModel.h" | |
20 #include "data/model/AggregateWaveModel.h" | |
21 #include "data/model/RangeSummarisableTimeValueModel.h" | |
22 #include "data/model/SparseTimeValueModel.h" | |
23 #include "data/model/AlignmentModel.h" | |
24 | |
25 #include "data/fileio/CSVFileReader.h" | |
26 | |
27 #include "transform/TransformFactory.h" | |
28 #include "transform/ModelTransformerFactory.h" | |
29 #include "transform/FeatureExtractionModelTransformer.h" | |
30 | |
31 #include <QProcess> | |
32 #include <QSettings> | |
33 #include <QApplication> | |
34 | |
35 bool | |
36 Align::alignModel(Document *doc, ModelId ref, ModelId other, QString &error) | |
37 { | |
38 QSettings settings; | |
39 settings.beginGroup("Preferences"); | |
40 bool useProgram = settings.value("use-external-alignment", false).toBool(); | |
41 QString program = settings.value("external-alignment-program", "").toString(); | |
42 settings.endGroup(); | |
43 | |
44 if (useProgram && (program != "")) { | |
45 return alignModelViaProgram(doc, ref, other, program, error); | |
46 } else { | |
47 return alignModelViaTransform(doc, ref, other, error); | |
48 } | |
49 } | |
50 | |
51 QString | |
52 Align::getAlignmentTransformName() | |
53 { | |
54 QSettings settings; | |
55 settings.beginGroup("Alignment"); | |
56 TransformId id = | |
57 settings.value("transform-id", | |
58 "vamp:match-vamp-plugin:match:path").toString(); | |
59 settings.endGroup(); | |
60 return id; | |
61 } | |
62 | |
63 QString | |
64 Align::getTuningDifferenceTransformName() | |
65 { | |
66 QSettings settings; | |
67 settings.beginGroup("Alignment"); | |
68 bool performPitchCompensation = | |
69 settings.value("align-pitch-aware", false).toBool(); | |
70 QString id = ""; | |
71 if (performPitchCompensation) { | |
72 id = settings.value | |
73 ("tuning-difference-transform-id", | |
74 "vamp:tuning-difference:tuning-difference:tuningfreq") | |
75 .toString(); | |
76 } | |
77 settings.endGroup(); | |
78 return id; | |
79 } | |
80 | |
81 bool | |
82 Align::canAlign() | |
83 { | |
84 TransformFactory *factory = TransformFactory::getInstance(); | |
85 TransformId id = getAlignmentTransformName(); | |
86 TransformId tdId = getTuningDifferenceTransformName(); | |
87 return factory->haveTransform(id) && | |
88 (tdId == "" || factory->haveTransform(tdId)); | |
89 } | |
90 | |
91 void | |
92 Align::abandonOngoingAlignment(ModelId otherId) | |
93 { | |
94 auto other = ModelById::getAs<RangeSummarisableTimeValueModel>(otherId); | |
95 if (!other) { | |
96 return; | |
97 } | |
98 | |
99 ModelId alignmentModelId = other->getAlignment(); | |
100 if (alignmentModelId.isNone()) { | |
101 return; | |
102 } | |
103 | |
104 SVCERR << "Align::abandonOngoingAlignment: An alignment is ongoing for model " | |
105 << otherId << " (alignment model id " << alignmentModelId | |
106 << "), abandoning it..." << endl; | |
107 | |
108 other->setAlignment({}); | |
109 | |
110 for (auto pp: m_pendingProcesses) { | |
111 if (alignmentModelId == pp.second) { | |
112 QProcess *process = pp.first; | |
113 m_pendingProcesses.erase(process); | |
114 SVCERR << "Align::abandonOngoingAlignment: Killing external " | |
115 << "alignment process " << process << "..." << endl; | |
116 delete process; // kills the process itself | |
117 break; | |
118 } | |
119 } | |
120 | |
121 if (m_pendingAlignments.find(alignmentModelId) != | |
122 m_pendingAlignments.end()) { | |
123 SVCERR << "Align::abandonOngoingAlignment: Releasing path output model " | |
124 << m_pendingAlignments[alignmentModelId] | |
125 << "..." << endl; | |
126 ModelById::release(m_pendingAlignments[alignmentModelId]); | |
127 SVCERR << "Align::abandonOngoingAlignment: Dropping alignment model " | |
128 << alignmentModelId | |
129 << " from pending alignments..." << endl; | |
130 m_pendingAlignments.erase(alignmentModelId); | |
131 } | |
132 | |
133 for (auto ptd: m_pendingTuningDiffs) { | |
134 if (alignmentModelId == ptd.second.alignment) { | |
135 SVCERR << "Align::abandonOngoingAlignment: Releasing preparatory model " | |
136 << ptd.second.preparatory << "..." << endl; | |
137 ModelById::release(ptd.second.preparatory); | |
138 SVCERR << "Align::abandonOngoingAlignment: Releasing pending tuning-diff model " | |
139 << ptd.first << "..." << endl; | |
140 ModelById::release(ptd.first); | |
141 SVCERR << "Align::abandonOngoingAlignment: Dropping tuning-diff model " | |
142 << ptd.first | |
143 << " from pending tuning diffs..." << endl; | |
144 m_pendingTuningDiffs.erase(ptd.first); | |
145 break; | |
146 } | |
147 } | |
148 | |
149 SVCERR << "Align::abandonOngoingAlignment: done" << endl; | |
150 } | |
151 | |
152 bool | |
153 Align::alignModelViaTransform(Document *doc, | |
154 ModelId referenceId, | |
155 ModelId otherId, | |
156 QString &error) | |
157 { | |
158 QMutexLocker locker (&m_mutex); | |
159 | |
160 auto reference = | |
161 ModelById::getAs<RangeSummarisableTimeValueModel>(referenceId); | |
162 auto other = | |
163 ModelById::getAs<RangeSummarisableTimeValueModel>(otherId); | |
164 | |
165 if (!reference || !other) return false; | |
166 | |
167 // There may be an alignment already happening; we should stop it, | |
168 // which we can do by discarding the output models for its | |
169 // transforms | |
170 abandonOngoingAlignment(otherId); | |
171 | |
172 // This involves creating a number of new models: | |
173 // | |
174 // 1. an AggregateWaveModel to provide the mixdowns of the main | |
175 // model and the new model in its two channels, as input to the | |
176 // MATCH plugin. We just call this one aggregateModel | |
177 // | |
178 // 2a. a SparseTimeValueModel which will be automatically created | |
179 // by FeatureExtractionModelTransformer when running the | |
180 // TuningDifference plugin to receive the relative tuning of the | |
181 // second model (if pitch-aware alignment is enabled in the | |
182 // preferences). We call this tuningDiffOutputModel. | |
183 // | |
184 // 2b. a SparseTimeValueModel which will be automatically created | |
185 // by FeatureExtractionPluginTransformer when running the MATCH | |
186 // plugin to perform alignment (so containing the alignment path). | |
187 // We call this one pathOutputModel. | |
188 // | |
189 // 2c. a SparseTimeValueModel used solely to provide faked | |
190 // completion information to the AlignmentModel while a | |
191 // TuningDifference calculation is going on. We call this | |
192 // preparatoryModel. | |
193 // | |
194 // 3. an AlignmentModel, which stores the path and carries out | |
195 // alignment lookups on it. We just call this one alignmentModel. | |
196 // | |
197 // Models 1 and 3 are registered with the document, which will | |
198 // eventually release them. We don't release them here except in | |
199 // the case where an activity fails before the point where we | |
200 // would otherwise have registered them with the document. | |
201 // | |
202 // Models 2a (tuningDiffOutputModel), 2b (pathOutputModel) and 2c | |
203 // (preparatoryModel) are not registered with the document. Model | |
204 // 2b (pathOutputModel) is not registered because we do not have a | |
205 // stable reference to the document at the point where it is | |
206 // created. Model 2c (preparatoryModel) is not registered because | |
207 // it is a bodge that we are embarrassed about, so we try to | |
208 // manage it ourselves without anyone else noticing. Model 2a is | |
209 // not registered for symmetry with the other two. These have to | |
210 // be released by us when finished with, but their lifespans do | |
211 // not extend beyond the end of the alignment procedure, so this | |
212 // should be ok. | |
213 | |
214 AggregateWaveModel::ChannelSpecList components; | |
215 | |
216 components.push_back | |
217 (AggregateWaveModel::ModelChannelSpec(referenceId, -1)); | |
218 | |
219 components.push_back | |
220 (AggregateWaveModel::ModelChannelSpec(otherId, -1)); | |
221 | |
222 auto aggregateModel = std::make_shared<AggregateWaveModel>(components); | |
223 auto aggregateModelId = ModelById::add(aggregateModel); | |
224 doc->addNonDerivedModel(aggregateModelId); | |
225 | |
226 auto alignmentModel = std::make_shared<AlignmentModel> | |
227 (referenceId, otherId, ModelId()); | |
228 auto alignmentModelId = ModelById::add(alignmentModel); | |
229 | |
230 TransformId tdId = getTuningDifferenceTransformName(); | |
231 | |
232 if (tdId == "") { | |
233 | |
234 if (beginTransformDrivenAlignment(aggregateModelId, | |
235 alignmentModelId)) { | |
236 other->setAlignment(alignmentModelId); | |
237 doc->addNonDerivedModel(alignmentModelId); | |
238 } else { | |
239 error = alignmentModel->getError(); | |
240 ModelById::release(alignmentModel); | |
241 return false; | |
242 } | |
243 | |
244 } else { | |
245 | |
246 // Have a tuning-difference transform id, so run it | |
247 // asynchronously first | |
248 | |
249 TransformFactory *tf = TransformFactory::getInstance(); | |
250 | |
251 Transform transform = tf->getDefaultTransformFor | |
252 (tdId, aggregateModel->getSampleRate()); | |
253 | |
254 transform.setParameter("maxduration", 60); | |
255 transform.setParameter("maxrange", 6); | |
256 transform.setParameter("finetuning", false); | |
257 | |
258 SVDEBUG << "Align::alignModel: Tuning difference transform step size " << transform.getStepSize() << ", block size " << transform.getBlockSize() << endl; | |
259 | |
260 ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance(); | |
261 | |
262 QString message; | |
263 ModelId tuningDiffOutputModelId = mtf->transform(transform, | |
264 aggregateModelId, | |
265 message); | |
266 | |
267 auto tuningDiffOutputModel = | |
268 ModelById::getAs<SparseTimeValueModel>(tuningDiffOutputModelId); | |
269 if (!tuningDiffOutputModel) { | |
270 SVCERR << "Align::alignModel: ERROR: Failed to create tuning-difference output model (no Tuning Difference plugin?)" << endl; | |
271 error = message; | |
272 ModelById::release(alignmentModel); | |
273 return false; | |
274 } | |
275 | |
276 other->setAlignment(alignmentModelId); | |
277 doc->addNonDerivedModel(alignmentModelId); | |
278 | |
279 connect(tuningDiffOutputModel.get(), | |
280 SIGNAL(completionChanged(ModelId)), | |
281 this, SLOT(tuningDifferenceCompletionChanged(ModelId))); | |
282 | |
283 TuningDiffRec rec; | |
284 rec.input = aggregateModelId; | |
285 rec.alignment = alignmentModelId; | |
286 | |
287 // This model exists only so that the AlignmentModel can get a | |
288 // completion value from somewhere while the tuning difference | |
289 // calculation is going on | |
290 auto preparatoryModel = std::make_shared<SparseTimeValueModel> | |
291 (aggregateModel->getSampleRate(), 1); | |
292 auto preparatoryModelId = ModelById::add(preparatoryModel); | |
293 preparatoryModel->setCompletion(0); | |
294 rec.preparatory = preparatoryModelId; | |
295 alignmentModel->setPathFrom(rec.preparatory); | |
296 | |
297 m_pendingTuningDiffs[tuningDiffOutputModelId] = rec; | |
298 | |
299 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; | |
300 } | |
301 | |
302 return true; | |
303 } | |
304 | |
305 void | |
306 Align::tuningDifferenceCompletionChanged(ModelId tuningDiffOutputModelId) | |
307 { | |
308 QMutexLocker locker(&m_mutex); | |
309 | |
310 if (m_pendingTuningDiffs.find(tuningDiffOutputModelId) == | |
311 m_pendingTuningDiffs.end()) { | |
312 SVDEBUG << "NOTE: Align::tuningDifferenceCompletionChanged: Model " | |
313 << tuningDiffOutputModelId | |
314 << " not found in pending tuning diff map, presuming " | |
315 << "completed or abandoned" << endl; | |
316 return; | |
317 } | |
318 | |
319 auto tuningDiffOutputModel = | |
320 ModelById::getAs<SparseTimeValueModel>(tuningDiffOutputModelId); | |
321 if (!tuningDiffOutputModel) { | |
322 SVCERR << "WARNING: Align::tuningDifferenceCompletionChanged: Model " | |
323 << tuningDiffOutputModelId | |
324 << " not known as SparseTimeValueModel" << endl; | |
325 return; | |
326 } | |
327 | |
328 TuningDiffRec rec = m_pendingTuningDiffs[tuningDiffOutputModelId]; | |
329 | |
330 auto alignmentModel = ModelById::getAs<AlignmentModel>(rec.alignment); | |
331 if (!alignmentModel) { | |
332 SVCERR << "WARNING: Align::tuningDifferenceCompletionChanged:" | |
333 << "alignment model has disappeared" << endl; | |
334 return; | |
335 } | |
336 | |
337 int completion = 0; | |
338 bool done = tuningDiffOutputModel->isReady(&completion); | |
339 | |
340 if (!done) { | |
341 // This will be the completion the alignment model reports, | |
342 // before the alignment actually begins. It goes up from 0 to | |
343 // 99 (not 100!) and then back to 0 again when we start | |
344 // calculating the actual path in the following phase | |
345 int clamped = (completion == 100 ? 99 : completion); | |
346 auto preparatoryModel = | |
347 ModelById::getAs<SparseTimeValueModel>(rec.preparatory); | |
348 if (preparatoryModel) { | |
349 preparatoryModel->setCompletion(clamped); | |
350 } | |
351 return; | |
352 } | |
353 | |
354 float tuningFrequency = 440.f; | |
355 | |
356 if (!tuningDiffOutputModel->isEmpty()) { | |
357 tuningFrequency = tuningDiffOutputModel->getAllEvents()[0].getValue(); | |
358 SVCERR << "Align::tuningDifferenceCompletionChanged: Reported tuning frequency = " << tuningFrequency << endl; | |
359 } else { | |
360 SVCERR << "Align::tuningDifferenceCompletionChanged: No tuning frequency reported" << endl; | |
361 } | |
362 | |
363 ModelById::release(tuningDiffOutputModel); | |
364 | |
365 alignmentModel->setPathFrom({}); // replace preparatoryModel | |
366 ModelById::release(rec.preparatory); | |
367 rec.preparatory = {}; | |
368 | |
369 m_pendingTuningDiffs.erase(tuningDiffOutputModelId); | |
370 | |
371 SVDEBUG << "Align::tuningDifferenceCompletionChanged: Erasing model " | |
372 << tuningDiffOutputModelId << " from pending tuning diffs and " | |
373 << "launching the alignment phase for alignment model " | |
374 << rec.alignment << " with tuning frequency " | |
375 << tuningFrequency << endl; | |
376 | |
377 beginTransformDrivenAlignment | |
378 (rec.input, rec.alignment, tuningFrequency); | |
379 } | |
380 | |
381 bool | |
382 Align::beginTransformDrivenAlignment(ModelId aggregateModelId, | |
383 ModelId alignmentModelId, | |
384 float tuningFrequency) | |
385 { | |
386 TransformId id = getAlignmentTransformName(); | |
387 | |
388 TransformFactory *tf = TransformFactory::getInstance(); | |
389 | |
390 auto aggregateModel = | |
391 ModelById::getAs<AggregateWaveModel>(aggregateModelId); | |
392 auto alignmentModel = | |
393 ModelById::getAs<AlignmentModel>(alignmentModelId); | |
394 | |
395 if (!aggregateModel || !alignmentModel) { | |
396 SVCERR << "Align::alignModel: ERROR: One or other of the aggregate & alignment models has disappeared" << endl; | |
397 return false; | |
398 } | |
399 | |
400 Transform transform = tf->getDefaultTransformFor | |
401 (id, aggregateModel->getSampleRate()); | |
402 | |
403 transform.setStepSize(transform.getBlockSize()/2); | |
404 transform.setParameter("serialise", 1); | |
405 transform.setParameter("smooth", 0); | |
406 transform.setParameter("zonewidth", 40); | |
407 transform.setParameter("noise", true); | |
408 transform.setParameter("minfreq", 500); | |
409 | |
410 int cents = 0; | |
411 | |
412 if (tuningFrequency != 0.f) { | |
413 transform.setParameter("freq2", tuningFrequency); | |
414 | |
415 double centsOffset = 0.f; | |
416 int pitch = Pitch::getPitchForFrequency(tuningFrequency, ¢sOffset); | |
417 cents = int(round((pitch - 69) * 100 + centsOffset)); | |
418 SVCERR << "frequency " << tuningFrequency << " yields cents offset " << centsOffset << " and pitch " << pitch << " -> cents " << cents << endl; | |
419 } | |
420 | |
421 alignmentModel->setRelativePitch(cents); | |
422 | |
423 SVDEBUG << "Align::alignModel: Alignment transform step size " << transform.getStepSize() << ", block size " << transform.getBlockSize() << endl; | |
424 | |
425 ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance(); | |
426 | |
427 QString message; | |
428 ModelId pathOutputModelId = mtf->transform | |
429 (transform, aggregateModelId, message); | |
430 | |
431 if (pathOutputModelId.isNone()) { | |
432 transform.setStepSize(0); | |
433 pathOutputModelId = mtf->transform | |
434 (transform, aggregateModelId, message); | |
435 } | |
436 | |
437 auto pathOutputModel = | |
438 ModelById::getAs<SparseTimeValueModel>(pathOutputModelId); | |
439 | |
440 //!!! callers will need to be updated to get error from | |
441 //!!! alignment model after initial call | |
442 | |
443 if (!pathOutputModel) { | |
444 SVCERR << "Align::alignModel: ERROR: Failed to create alignment path (no MATCH plugin?)" << endl; | |
445 alignmentModel->setError(message); | |
446 return false; | |
447 } | |
448 | |
449 pathOutputModel->setCompletion(0); | |
450 alignmentModel->setPathFrom(pathOutputModelId); | |
451 | |
452 m_pendingAlignments[alignmentModelId] = pathOutputModelId; | |
453 | |
454 connect(alignmentModel.get(), SIGNAL(completionChanged(ModelId)), | |
455 this, SLOT(alignmentCompletionChanged(ModelId))); | |
456 | |
457 return true; | |
458 } | |
459 | |
460 void | |
461 Align::alignmentCompletionChanged(ModelId alignmentModelId) | |
462 { | |
463 QMutexLocker locker (&m_mutex); | |
464 | |
465 auto alignmentModel = ModelById::getAs<AlignmentModel>(alignmentModelId); | |
466 | |
467 if (alignmentModel && alignmentModel->isReady()) { | |
468 | |
469 if (m_pendingAlignments.find(alignmentModelId) != | |
470 m_pendingAlignments.end()) { | |
471 ModelId pathOutputModelId = m_pendingAlignments[alignmentModelId]; | |
472 ModelById::release(pathOutputModelId); | |
473 m_pendingAlignments.erase(alignmentModelId); | |
474 } | |
475 | |
476 disconnect(alignmentModel.get(), | |
477 SIGNAL(completionChanged(ModelId)), | |
478 this, SLOT(alignmentCompletionChanged(ModelId))); | |
479 emit alignmentComplete(alignmentModelId); | |
480 } | |
481 } | |
482 | |
483 bool | |
484 Align::alignModelViaProgram(Document *doc, | |
485 ModelId referenceId, | |
486 ModelId otherId, | |
487 QString program, | |
488 QString &error) | |
489 { | |
490 // Run an external program, passing to it paths to the main | |
491 // model's audio file and the new model's audio file. It returns | |
492 // the path in CSV form through stdout. | |
493 | |
494 auto reference = ModelById::getAs<ReadOnlyWaveFileModel>(referenceId); | |
495 auto other = ModelById::getAs<ReadOnlyWaveFileModel>(otherId); | |
496 if (!reference || !other) { | |
497 SVCERR << "ERROR: Align::alignModelViaProgram: Can't align non-read-only models via program (no local filename available)" << endl; | |
498 return false; | |
499 } | |
500 | |
501 while (!reference->isReady(nullptr) || !other->isReady(nullptr)) { | |
502 qApp->processEvents(); | |
503 } | |
504 | |
505 QString refPath = reference->getLocalFilename(); | |
506 if (refPath == "") { | |
507 refPath = FileSource(reference->getLocation()).getLocalFilename(); | |
508 } | |
509 | |
510 QString otherPath = other->getLocalFilename(); | |
511 if (otherPath == "") { | |
512 otherPath = FileSource(other->getLocation()).getLocalFilename(); | |
513 } | |
514 | |
515 if (refPath == "" || otherPath == "") { | |
516 error = "Failed to find local filepath for wave-file model"; | |
517 return false; | |
518 } | |
519 | |
520 QProcess *process = nullptr; | |
521 ModelId alignmentModelId = {}; | |
522 | |
523 { | |
524 QMutexLocker locker (&m_mutex); | |
525 | |
526 auto alignmentModel = | |
527 std::make_shared<AlignmentModel>(referenceId, otherId, ModelId()); | |
528 | |
529 alignmentModelId = ModelById::add(alignmentModel); | |
530 other->setAlignment(alignmentModelId); | |
531 | |
532 process = new QProcess; | |
533 process->setProcessChannelMode(QProcess::ForwardedErrorChannel); | |
534 | |
535 connect(process, | |
536 SIGNAL(finished(int, QProcess::ExitStatus)), | |
537 this, | |
538 SLOT(alignmentProgramFinished(int, QProcess::ExitStatus))); | |
539 | |
540 m_pendingProcesses[process] = alignmentModelId; | |
541 } | |
542 | |
543 QStringList args; | |
544 args << refPath << otherPath; | |
545 | |
546 SVCERR << "Align::alignModelViaProgram: Starting program \"" | |
547 << program << "\" with args: "; | |
548 for (auto a: args) { | |
549 SVCERR << "\"" << a << "\" "; | |
550 } | |
551 SVCERR << endl; | |
552 | |
553 process->start(program, args); | |
554 | |
555 bool success = process->waitForStarted(); | |
556 | |
557 { | |
558 QMutexLocker locker(&m_mutex); | |
559 | |
560 if (!success) { | |
561 | |
562 SVCERR << "ERROR: Align::alignModelViaProgram: " | |
563 << "Program did not start" << endl; | |
564 error = "Alignment program \"" + program + "\" did not start"; | |
565 | |
566 m_pendingProcesses.erase(process); | |
567 other->setAlignment({}); | |
568 ModelById::release(alignmentModelId); | |
569 delete process; | |
570 | |
571 } else { | |
572 doc->addNonDerivedModel(alignmentModelId); | |
573 } | |
574 } | |
575 | |
576 return success; | |
577 } | |
578 | |
579 void | |
580 Align::alignmentProgramFinished(int exitCode, QProcess::ExitStatus status) | |
581 { | |
582 QMutexLocker locker (&m_mutex); | |
583 | |
584 SVCERR << "Align::alignmentProgramFinished" << endl; | |
585 | |
586 QProcess *process = qobject_cast<QProcess *>(sender()); | |
587 | |
588 if (m_pendingProcesses.find(process) == m_pendingProcesses.end()) { | |
589 SVCERR << "ERROR: Align::alignmentProgramFinished: Process " << process | |
590 << " not found in process model map!" << endl; | |
591 return; | |
592 } | |
593 | |
594 ModelId alignmentModelId = m_pendingProcesses[process]; | |
595 auto alignmentModel = ModelById::getAs<AlignmentModel>(alignmentModelId); | |
596 if (!alignmentModel) return; | |
597 | |
598 if (exitCode == 0 && status == 0) { | |
599 | |
600 CSVFormat format; | |
601 format.setModelType(CSVFormat::TwoDimensionalModel); | |
602 format.setTimingType(CSVFormat::ExplicitTiming); | |
603 format.setTimeUnits(CSVFormat::TimeSeconds); | |
604 format.setColumnCount(2); | |
605 // The output format has time in the reference file first, and | |
606 // time in the "other" file in the second column. This is a | |
607 // more natural approach for a command-line alignment tool, | |
608 // but it's the opposite of what we expect for native | |
609 // alignment paths, which map from "other" file to | |
610 // reference. These column purpose settings reflect that. | |
611 format.setColumnPurpose(1, CSVFormat::ColumnStartTime); | |
612 format.setColumnPurpose(0, CSVFormat::ColumnValue); | |
613 format.setAllowQuoting(false); | |
614 format.setSeparator(','); | |
615 | |
616 CSVFileReader reader(process, format, alignmentModel->getSampleRate()); | |
617 if (!reader.isOK()) { | |
618 SVCERR << "ERROR: Align::alignmentProgramFinished: Failed to parse output" | |
619 << endl; | |
620 alignmentModel->setError | |
621 (QString("Failed to parse output of program: %1") | |
622 .arg(reader.getError())); | |
623 goto done; | |
624 } | |
625 | |
626 //!!! to use ById? | |
627 | |
628 Model *csvOutput = reader.load(); | |
629 | |
630 SparseTimeValueModel *path = | |
631 qobject_cast<SparseTimeValueModel *>(csvOutput); | |
632 if (!path) { | |
633 SVCERR << "ERROR: Align::alignmentProgramFinished: Output did not convert to sparse time-value model" | |
634 << endl; | |
635 alignmentModel->setError | |
636 ("Output of program did not produce sparse time-value model"); | |
637 delete csvOutput; | |
638 goto done; | |
639 } | |
640 | |
641 if (path->isEmpty()) { | |
642 SVCERR << "ERROR: Align::alignmentProgramFinished: Output contained no mappings" | |
643 << endl; | |
644 alignmentModel->setError | |
645 ("Output of alignment program contained no mappings"); | |
646 delete path; | |
647 goto done; | |
648 } | |
649 | |
650 SVCERR << "Align::alignmentProgramFinished: Setting alignment path (" | |
651 << path->getEventCount() << " point(s))" << endl; | |
652 | |
653 auto pathId = | |
654 ModelById::add(std::shared_ptr<SparseTimeValueModel>(path)); | |
655 alignmentModel->setPathFrom(pathId); | |
656 | |
657 emit alignmentComplete(alignmentModelId); | |
658 | |
659 ModelById::release(pathId); | |
660 | |
661 } else { | |
662 SVCERR << "ERROR: Align::alignmentProgramFinished: Aligner program " | |
663 << "failed: exit code " << exitCode << ", status " << status | |
664 << endl; | |
665 alignmentModel->setError | |
666 ("Aligner process returned non-zero exit status"); | |
667 } | |
668 | |
669 done: | |
670 m_pendingProcesses.erase(process); | |
671 delete process; | |
672 } | |
673 |