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