Mercurial > hg > svapp
comparison framework/Align.cpp @ 520:c3648c667a0b 3.0-integration
Merge from branch "alignment-simple"
author | Chris Cannam |
---|---|
date | Thu, 21 Apr 2016 15:06:43 +0100 |
parents | 51befd6165a3 |
children | b23bebfdfaba |
comparison
equal
deleted
inserted
replaced
518:f7ec9e410108 | 520:c3648c667a0b |
---|---|
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 This file copyright 2006 Chris Cannam and QMUL. | |
8 | |
9 This program is free software; you can redistribute it and/or | |
10 modify it under the terms of the GNU General Public License as | |
11 published by the Free Software Foundation; either version 2 of the | |
12 License, or (at your option) any later version. See the file | |
13 COPYING included with this distribution for more information. | |
14 */ | |
15 | |
16 #include "Align.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(Model *ref, Model *other) | |
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(ref, other, program); | |
46 } else { | |
47 return alignModelViaTransform(ref, other); | |
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 bool | |
64 Align::canAlign() | |
65 { | |
66 TransformId id = getAlignmentTransformName(); | |
67 TransformFactory *factory = TransformFactory::getInstance(); | |
68 return factory->haveTransform(id); | |
69 } | |
70 | |
71 bool | |
72 Align::alignModelViaTransform(Model *ref, Model *other) | |
73 { | |
74 RangeSummarisableTimeValueModel *reference = qobject_cast | |
75 <RangeSummarisableTimeValueModel *>(ref); | |
76 | |
77 RangeSummarisableTimeValueModel *rm = qobject_cast | |
78 <RangeSummarisableTimeValueModel *>(other); | |
79 | |
80 if (!reference || !rm) return false; // but this should have been tested already | |
81 | |
82 // This involves creating three new models: | |
83 | |
84 // 1. an AggregateWaveModel to provide the mixdowns of the main | |
85 // model and the new model in its two channels, as input to the | |
86 // MATCH plugin | |
87 | |
88 // 2. a SparseTimeValueModel, which is the model automatically | |
89 // created by FeatureExtractionPluginTransformer when running the | |
90 // MATCH plugin (thus containing the alignment path) | |
91 | |
92 // 3. an AlignmentModel, which stores the path model and carries | |
93 // out alignment lookups on it. | |
94 | |
95 // The first two of these are provided as arguments to the | |
96 // constructor for the third, which takes responsibility for | |
97 // deleting them. The AlignmentModel, meanwhile, is passed to the | |
98 // new model we are aligning, which also takes responsibility for | |
99 // it. We should not have to delete any of these new models here. | |
100 | |
101 AggregateWaveModel::ChannelSpecList components; | |
102 | |
103 components.push_back(AggregateWaveModel::ModelChannelSpec | |
104 (reference, -1)); | |
105 | |
106 components.push_back(AggregateWaveModel::ModelChannelSpec | |
107 (rm, -1)); | |
108 | |
109 Model *aggregateModel = new AggregateWaveModel(components); | |
110 ModelTransformer::Input aggregate(aggregateModel); | |
111 | |
112 TransformId id = getAlignmentTransformName(); | |
113 | |
114 TransformFactory *tf = TransformFactory::getInstance(); | |
115 | |
116 Transform transform = tf->getDefaultTransformFor | |
117 (id, aggregateModel->getSampleRate()); | |
118 | |
119 transform.setStepSize(transform.getBlockSize()/2); | |
120 transform.setParameter("serialise", 1); | |
121 transform.setParameter("smooth", 0); | |
122 | |
123 SVDEBUG << "Align::alignModel: Alignment transform step size " << transform.getStepSize() << ", block size " << transform.getBlockSize() << endl; | |
124 | |
125 ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance(); | |
126 | |
127 QString message; | |
128 Model *transformOutput = mtf->transform(transform, aggregate, message); | |
129 | |
130 if (!transformOutput) { | |
131 transform.setStepSize(0); | |
132 transformOutput = mtf->transform(transform, aggregate, message); | |
133 } | |
134 | |
135 SparseTimeValueModel *path = dynamic_cast<SparseTimeValueModel *> | |
136 (transformOutput); | |
137 | |
138 if (!path) { | |
139 cerr << "Align::alignModel: ERROR: Failed to create alignment path (no MATCH plugin?)" << endl; | |
140 delete transformOutput; | |
141 delete aggregateModel; | |
142 m_error = message; | |
143 return false; | |
144 } | |
145 | |
146 path->setCompletion(0); | |
147 | |
148 AlignmentModel *alignmentModel = new AlignmentModel | |
149 (reference, other, aggregateModel, path); | |
150 | |
151 connect(alignmentModel, SIGNAL(completionChanged()), | |
152 this, SLOT(alignmentCompletionChanged())); | |
153 | |
154 rm->setAlignment(alignmentModel); | |
155 | |
156 return true; | |
157 } | |
158 | |
159 void | |
160 Align::alignmentCompletionChanged() | |
161 { | |
162 AlignmentModel *am = qobject_cast<AlignmentModel *>(sender()); | |
163 if (!am) return; | |
164 if (am->isReady()) { | |
165 disconnect(am, SIGNAL(completionChanged()), | |
166 this, SLOT(alignmentCompletionChanged())); | |
167 emit alignmentComplete(am); | |
168 } | |
169 } | |
170 | |
171 bool | |
172 Align::alignModelViaProgram(Model *ref, Model *other, QString program) | |
173 { | |
174 WaveFileModel *reference = qobject_cast<WaveFileModel *>(ref); | |
175 WaveFileModel *rm = qobject_cast<WaveFileModel *>(other); | |
176 | |
177 if (!reference || !rm) { | |
178 return false; // but this should have been tested already | |
179 } | |
180 | |
181 while (!reference->isReady(0) || !rm->isReady(0)) { | |
182 qApp->processEvents(); | |
183 } | |
184 | |
185 // Run an external program, passing to it paths to the main | |
186 // model's audio file and the new model's audio file. It returns | |
187 // the path in CSV form through stdout. | |
188 | |
189 ReadOnlyWaveFileModel *roref = qobject_cast<ReadOnlyWaveFileModel *>(reference); | |
190 ReadOnlyWaveFileModel *rorm = qobject_cast<ReadOnlyWaveFileModel *>(rm); | |
191 if (!roref || !rorm) { | |
192 cerr << "ERROR: Align::alignModelViaProgram: Can't align non-read-only models via program (no local filename available)" << endl; | |
193 return false; | |
194 } | |
195 | |
196 QString refPath = roref->getLocalFilename(); | |
197 QString otherPath = rorm->getLocalFilename(); | |
198 | |
199 if (refPath == "" || otherPath == "") { | |
200 m_error = "Failed to find local filepath for wave-file model"; | |
201 return false; | |
202 } | |
203 | |
204 m_error = ""; | |
205 | |
206 AlignmentModel *alignmentModel = new AlignmentModel(reference, other, 0, 0); | |
207 rm->setAlignment(alignmentModel); | |
208 | |
209 QProcess *process = new QProcess; | |
210 QStringList args; | |
211 args << refPath << otherPath; | |
212 | |
213 connect(process, SIGNAL(finished(int, QProcess::ExitStatus)), | |
214 this, SLOT(alignmentProgramFinished(int, QProcess::ExitStatus))); | |
215 | |
216 m_processModels[process] = alignmentModel; | |
217 process->start(program, args); | |
218 | |
219 bool success = process->waitForStarted(); | |
220 | |
221 if (!success) { | |
222 cerr << "ERROR: Align::alignModelViaProgram: Program did not start" | |
223 << endl; | |
224 m_error = "Alignment program could not be started"; | |
225 m_processModels.erase(process); | |
226 rm->setAlignment(0); // deletes alignmentModel as well | |
227 delete process; | |
228 } | |
229 | |
230 return success; | |
231 } | |
232 | |
233 void | |
234 Align::alignmentProgramFinished(int exitCode, QProcess::ExitStatus status) | |
235 { | |
236 cerr << "Align::alignmentProgramFinished" << endl; | |
237 | |
238 QProcess *process = qobject_cast<QProcess *>(sender()); | |
239 | |
240 if (m_processModels.find(process) == m_processModels.end()) { | |
241 cerr << "ERROR: Align::alignmentProgramFinished: Process " << process | |
242 << " not found in process model map!" << endl; | |
243 return; | |
244 } | |
245 | |
246 AlignmentModel *alignmentModel = m_processModels[process]; | |
247 | |
248 if (exitCode == 0 && status == 0) { | |
249 | |
250 CSVFormat format; | |
251 format.setModelType(CSVFormat::TwoDimensionalModel); | |
252 format.setTimingType(CSVFormat::ExplicitTiming); | |
253 format.setTimeUnits(CSVFormat::TimeSeconds); | |
254 format.setColumnCount(2); | |
255 // The output format has time in the reference file first, and | |
256 // time in the "other" file in the second column. This is a | |
257 // more natural approach for a command-line alignment tool, | |
258 // but it's the opposite of what we expect for native | |
259 // alignment paths, which map from "other" file to | |
260 // reference. These column purpose settings reflect that. | |
261 format.setColumnPurpose(1, CSVFormat::ColumnStartTime); | |
262 format.setColumnPurpose(0, CSVFormat::ColumnValue); | |
263 format.setAllowQuoting(false); | |
264 format.setSeparator(','); | |
265 | |
266 CSVFileReader reader(process, format, alignmentModel->getSampleRate()); | |
267 if (!reader.isOK()) { | |
268 cerr << "ERROR: Align::alignmentProgramFinished: Failed to parse output" | |
269 << endl; | |
270 m_error = QString("Failed to parse output of program: %1") | |
271 .arg(reader.getError()); | |
272 goto done; | |
273 } | |
274 | |
275 Model *csvOutput = reader.load(); | |
276 | |
277 SparseTimeValueModel *path = qobject_cast<SparseTimeValueModel *>(csvOutput); | |
278 if (!path) { | |
279 cerr << "ERROR: Align::alignmentProgramFinished: Output did not convert to sparse time-value model" | |
280 << endl; | |
281 m_error = QString("Output of program did not produce sparse time-value model"); | |
282 goto done; | |
283 } | |
284 | |
285 if (path->getPoints().empty()) { | |
286 cerr << "ERROR: Align::alignmentProgramFinished: Output contained no mappings" | |
287 << endl; | |
288 m_error = QString("Output of alignment program contained no mappings"); | |
289 goto done; | |
290 } | |
291 | |
292 cerr << "Align::alignmentProgramFinished: Setting alignment path (" | |
293 << path->getPoints().size() << " point(s))" << endl; | |
294 | |
295 alignmentModel->setPathFrom(path); | |
296 | |
297 emit alignmentComplete(alignmentModel); | |
298 | |
299 } else { | |
300 cerr << "ERROR: Align::alignmentProgramFinished: Aligner program " | |
301 << "failed: exit code " << exitCode << ", status " << status | |
302 << endl; | |
303 m_error = "Aligner process returned non-zero exit status"; | |
304 } | |
305 | |
306 done: | |
307 m_processModels.erase(process); | |
308 delete process; | |
309 } | |
310 |