Mercurial > hg > svapp
comparison align/MATCHAligner.cpp @ 777:87d33e79855b pitch-align
Rename TransformAligner -> MATCHAligner. It is still specific to MATCH, and at this point I think it's simpler to leave it that way and reduce confusion with the TransformDTWAligner which is much more generic.
author | Chris Cannam |
---|---|
date | Thu, 25 Jun 2020 17:43:10 +0100 |
parents | align/TransformAligner.cpp@a316cb6fed81 |
children | b651dc5ff555 |
comparison
equal
deleted
inserted
replaced
776:32e66fcc4cb7 | 777:87d33e79855b |
---|---|
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 "MATCHAligner.h" | |
16 | |
17 #include "data/model/SparseTimeValueModel.h" | |
18 #include "data/model/RangeSummarisableTimeValueModel.h" | |
19 #include "data/model/AlignmentModel.h" | |
20 #include "data/model/AggregateWaveModel.h" | |
21 | |
22 #include "framework/Document.h" | |
23 | |
24 #include "transform/TransformFactory.h" | |
25 #include "transform/ModelTransformerFactory.h" | |
26 #include "transform/FeatureExtractionModelTransformer.h" | |
27 | |
28 #include <QSettings> | |
29 | |
30 MATCHAligner::MATCHAligner(Document *doc, | |
31 ModelId reference, | |
32 ModelId toAlign, | |
33 bool withTuningDifference) : | |
34 m_document(doc), | |
35 m_reference(reference), | |
36 m_toAlign(toAlign), | |
37 m_withTuningDifference(withTuningDifference), | |
38 m_tuningFrequency(440.f), | |
39 m_incomplete(true) | |
40 { | |
41 } | |
42 | |
43 MATCHAligner::~MATCHAligner() | |
44 { | |
45 if (m_incomplete) { | |
46 auto other = | |
47 ModelById::getAs<RangeSummarisableTimeValueModel>(m_toAlign); | |
48 if (other) { | |
49 other->setAlignment({}); | |
50 } | |
51 } | |
52 | |
53 ModelById::release(m_tuningDiffOutputModel); | |
54 ModelById::release(m_pathOutputModel); | |
55 } | |
56 | |
57 QString | |
58 MATCHAligner::getAlignmentTransformName() | |
59 { | |
60 QSettings settings; | |
61 settings.beginGroup("Alignment"); | |
62 TransformId id = settings.value | |
63 ("transform-id", | |
64 "vamp:match-vamp-plugin:match:path").toString(); | |
65 settings.endGroup(); | |
66 return id; | |
67 } | |
68 | |
69 QString | |
70 MATCHAligner::getTuningDifferenceTransformName() | |
71 { | |
72 QSettings settings; | |
73 settings.beginGroup("Alignment"); | |
74 TransformId id = settings.value | |
75 ("tuning-difference-transform-id", | |
76 "vamp:tuning-difference:tuning-difference:tuningfreq") | |
77 .toString(); | |
78 settings.endGroup(); | |
79 return id; | |
80 } | |
81 | |
82 bool | |
83 MATCHAligner::isAvailable() | |
84 { | |
85 TransformFactory *factory = TransformFactory::getInstance(); | |
86 TransformId id = getAlignmentTransformName(); | |
87 TransformId tdId = getTuningDifferenceTransformName(); | |
88 return factory->haveTransform(id) && | |
89 (tdId == "" || factory->haveTransform(tdId)); | |
90 } | |
91 | |
92 void | |
93 MATCHAligner::begin() | |
94 { | |
95 auto reference = | |
96 ModelById::getAs<RangeSummarisableTimeValueModel>(m_reference); | |
97 auto other = | |
98 ModelById::getAs<RangeSummarisableTimeValueModel>(m_toAlign); | |
99 | |
100 if (!reference || !other) return; | |
101 | |
102 // This involves creating a number of new models: | |
103 // | |
104 // 1. an AggregateWaveModel to provide the mixdowns of the main | |
105 // model and the new model in its two channels, as input to the | |
106 // MATCH plugin. We just call this one aggregateModel | |
107 // | |
108 // 2a. a SparseTimeValueModel which will be automatically created | |
109 // by FeatureExtractionModelTransformer when running the | |
110 // TuningDifference plugin to receive the relative tuning of the | |
111 // second model (if pitch-aware alignment is enabled in the | |
112 // preferences). This is m_tuningDiffOutputModel. | |
113 // | |
114 // 2b. a SparseTimeValueModel which will be automatically created | |
115 // by FeatureExtractionPluginTransformer when running the MATCH | |
116 // plugin to perform alignment (so containing the alignment path). | |
117 // This is m_pathOutputModel. | |
118 // | |
119 // 3. an AlignmentModel, which stores the path and carries out | |
120 // alignment lookups on it. This one is m_alignmentModel. | |
121 // | |
122 // Models 1 and 3 are registered with the document, which will | |
123 // eventually release them. We don't release them here except in | |
124 // the case where an activity fails before the point where we | |
125 // would otherwise have registered them with the document. | |
126 // | |
127 // Models 2a (m_tuningDiffOutputModel) and 2b (m_pathOutputModel) | |
128 // are not registered with the document, because they are not | |
129 // intended to persist. These have to be released by us when | |
130 // finished with, but their lifespans do not extend beyond the end | |
131 // of the alignment procedure, so this should be ok. | |
132 | |
133 AggregateWaveModel::ChannelSpecList components; | |
134 components.push_back | |
135 (AggregateWaveModel::ModelChannelSpec(m_reference, -1)); | |
136 | |
137 components.push_back | |
138 (AggregateWaveModel::ModelChannelSpec(m_toAlign, -1)); | |
139 | |
140 auto aggregateModel = std::make_shared<AggregateWaveModel>(components); | |
141 m_aggregateModel = ModelById::add(aggregateModel); | |
142 m_document->addNonDerivedModel(m_aggregateModel); | |
143 | |
144 auto alignmentModel = std::make_shared<AlignmentModel> | |
145 (m_reference, m_toAlign, ModelId()); | |
146 m_alignmentModel = ModelById::add(alignmentModel); | |
147 | |
148 TransformId tdId; | |
149 if (m_withTuningDifference) { | |
150 tdId = getTuningDifferenceTransformName(); | |
151 } | |
152 | |
153 if (tdId == "") { | |
154 | |
155 if (beginAlignmentPhase()) { | |
156 other->setAlignment(m_alignmentModel); | |
157 m_document->addNonDerivedModel(m_alignmentModel); | |
158 } else { | |
159 QString error = alignmentModel->getError(); | |
160 ModelById::release(alignmentModel); | |
161 emit failed(m_toAlign, error); | |
162 return; | |
163 } | |
164 | |
165 } else { | |
166 | |
167 // Have a tuning-difference transform id, so run it | |
168 // asynchronously first | |
169 | |
170 TransformFactory *tf = TransformFactory::getInstance(); | |
171 | |
172 Transform transform = tf->getDefaultTransformFor | |
173 (tdId, aggregateModel->getSampleRate()); | |
174 | |
175 transform.setParameter("maxduration", 60); | |
176 transform.setParameter("maxrange", 6); | |
177 transform.setParameter("finetuning", false); | |
178 | |
179 SVDEBUG << "MATCHAligner: Tuning difference transform step size " << transform.getStepSize() << ", block size " << transform.getBlockSize() << endl; | |
180 | |
181 ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance(); | |
182 | |
183 QString message; | |
184 m_tuningDiffOutputModel = mtf->transform(transform, | |
185 m_aggregateModel, | |
186 message); | |
187 | |
188 auto tuningDiffOutputModel = | |
189 ModelById::getAs<SparseTimeValueModel>(m_tuningDiffOutputModel); | |
190 if (!tuningDiffOutputModel) { | |
191 SVCERR << "Align::alignModel: ERROR: Failed to create tuning-difference output model (no Tuning Difference plugin?)" << endl; | |
192 ModelById::release(alignmentModel); | |
193 emit failed(m_toAlign, message); | |
194 return; | |
195 } | |
196 | |
197 other->setAlignment(m_alignmentModel); | |
198 m_document->addNonDerivedModel(m_alignmentModel); | |
199 | |
200 connect(tuningDiffOutputModel.get(), | |
201 SIGNAL(completionChanged(ModelId)), | |
202 this, SLOT(tuningDifferenceCompletionChanged(ModelId))); | |
203 } | |
204 } | |
205 | |
206 void | |
207 MATCHAligner::tuningDifferenceCompletionChanged(ModelId tuningDiffOutputModelId) | |
208 { | |
209 if (m_tuningDiffOutputModel.isNone()) { | |
210 // we're done, this is probably a spurious queued event | |
211 return; | |
212 } | |
213 | |
214 if (tuningDiffOutputModelId != m_tuningDiffOutputModel) { | |
215 SVCERR << "WARNING: MATCHAligner::tuningDifferenceCompletionChanged: Model " | |
216 << tuningDiffOutputModelId | |
217 << " is not ours! (ours is " | |
218 << m_tuningDiffOutputModel << ")" << endl; | |
219 return; | |
220 } | |
221 | |
222 auto tuningDiffOutputModel = | |
223 ModelById::getAs<SparseTimeValueModel>(m_tuningDiffOutputModel); | |
224 if (!tuningDiffOutputModel) { | |
225 SVCERR << "WARNING: MATCHAligner::tuningDifferenceCompletionChanged: Model " | |
226 << tuningDiffOutputModelId | |
227 << " not known as SparseTimeValueModel" << endl; | |
228 return; | |
229 } | |
230 | |
231 auto alignmentModel = ModelById::getAs<AlignmentModel>(m_alignmentModel); | |
232 if (!alignmentModel) { | |
233 SVCERR << "WARNING: MATCHAligner::tuningDifferenceCompletionChanged:" | |
234 << "alignment model has disappeared" << endl; | |
235 return; | |
236 } | |
237 | |
238 int completion = 0; | |
239 bool done = tuningDiffOutputModel->isReady(&completion); | |
240 | |
241 SVDEBUG << "MATCHAligner::tuningDifferenceCompletionChanged: model " | |
242 << m_tuningDiffOutputModel << ", completion = " << completion | |
243 << ", done = " << done << endl; | |
244 | |
245 if (!done) { | |
246 // This will be the completion the alignment model reports, | |
247 // before the alignment actually begins | |
248 alignmentModel->setCompletion(completion / 2); | |
249 return; | |
250 } | |
251 | |
252 m_tuningFrequency = 440.f; | |
253 | |
254 if (!tuningDiffOutputModel->isEmpty()) { | |
255 m_tuningFrequency = tuningDiffOutputModel->getAllEvents()[0].getValue(); | |
256 SVCERR << "MATCHAligner::tuningDifferenceCompletionChanged: Reported tuning frequency = " << m_tuningFrequency << endl; | |
257 } else { | |
258 SVCERR << "MATCHAligner::tuningDifferenceCompletionChanged: No tuning frequency reported" << endl; | |
259 } | |
260 | |
261 ModelById::release(tuningDiffOutputModel); | |
262 m_tuningDiffOutputModel = {}; | |
263 | |
264 beginAlignmentPhase(); | |
265 } | |
266 | |
267 bool | |
268 MATCHAligner::beginAlignmentPhase() | |
269 { | |
270 TransformId id = getAlignmentTransformName(); | |
271 | |
272 SVDEBUG << "MATCHAligner::beginAlignmentPhase: transform is " | |
273 << id << endl; | |
274 | |
275 TransformFactory *tf = TransformFactory::getInstance(); | |
276 | |
277 auto aggregateModel = | |
278 ModelById::getAs<AggregateWaveModel>(m_aggregateModel); | |
279 auto alignmentModel = | |
280 ModelById::getAs<AlignmentModel>(m_alignmentModel); | |
281 | |
282 if (!aggregateModel || !alignmentModel) { | |
283 SVCERR << "MATCHAligner::alignModel: ERROR: One or other of the aggregate & alignment models has disappeared" << endl; | |
284 return false; | |
285 } | |
286 | |
287 Transform transform = tf->getDefaultTransformFor | |
288 (id, aggregateModel->getSampleRate()); | |
289 | |
290 transform.setStepSize(transform.getBlockSize()/2); | |
291 transform.setParameter("serialise", 1); | |
292 transform.setParameter("smooth", 0); | |
293 transform.setParameter("zonewidth", 40); | |
294 transform.setParameter("noise", true); | |
295 transform.setParameter("minfreq", 500); | |
296 | |
297 int cents = 0; | |
298 | |
299 if (m_tuningFrequency != 0.f) { | |
300 transform.setParameter("freq2", m_tuningFrequency); | |
301 | |
302 double centsOffset = 0.f; | |
303 int pitch = Pitch::getPitchForFrequency(m_tuningFrequency, | |
304 ¢sOffset); | |
305 cents = int(round((pitch - 69) * 100 + centsOffset)); | |
306 SVCERR << "MATCHAligner: frequency " << m_tuningFrequency | |
307 << " yields cents offset " << centsOffset | |
308 << " and pitch " << pitch << " -> cents " << cents << endl; | |
309 } | |
310 | |
311 alignmentModel->setRelativePitch(cents); | |
312 | |
313 SVDEBUG << "MATCHAligner: Alignment transform step size " | |
314 << transform.getStepSize() << ", block size " | |
315 << transform.getBlockSize() << endl; | |
316 | |
317 ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance(); | |
318 | |
319 QString message; | |
320 m_pathOutputModel = mtf->transform | |
321 (transform, m_aggregateModel, message); | |
322 | |
323 if (m_pathOutputModel.isNone()) { | |
324 transform.setStepSize(0); | |
325 m_pathOutputModel = mtf->transform | |
326 (transform, m_aggregateModel, message); | |
327 } | |
328 | |
329 auto pathOutputModel = | |
330 ModelById::getAs<SparseTimeValueModel>(m_pathOutputModel); | |
331 | |
332 //!!! callers will need to be updated to get error from | |
333 //!!! alignment model after initial call | |
334 | |
335 if (!pathOutputModel) { | |
336 SVCERR << "MATCHAligner: ERROR: Failed to create alignment path (no MATCH plugin?)" << endl; | |
337 alignmentModel->setError(message); | |
338 return false; | |
339 } | |
340 | |
341 pathOutputModel->setCompletion(0); | |
342 alignmentModel->setPathFrom(m_pathOutputModel); | |
343 | |
344 connect(pathOutputModel.get(), SIGNAL(completionChanged(ModelId)), | |
345 this, SLOT(alignmentCompletionChanged(ModelId))); | |
346 | |
347 return true; | |
348 } | |
349 | |
350 void | |
351 MATCHAligner::alignmentCompletionChanged(ModelId pathOutputModelId) | |
352 { | |
353 if (pathOutputModelId != m_pathOutputModel) { | |
354 SVCERR << "WARNING: MATCHAligner::alignmentCompletionChanged: Model " | |
355 << pathOutputModelId | |
356 << " is not ours! (ours is " | |
357 << m_pathOutputModel << ")" << endl; | |
358 return; | |
359 } | |
360 | |
361 auto pathOutputModel = | |
362 ModelById::getAs<SparseTimeValueModel>(m_pathOutputModel); | |
363 if (!pathOutputModel) { | |
364 SVCERR << "WARNING: MATCHAligner::alignmentCompletionChanged: Path output model " | |
365 << m_pathOutputModel << " no longer exists" << endl; | |
366 return; | |
367 } | |
368 | |
369 int completion = 0; | |
370 bool done = pathOutputModel->isReady(&completion); | |
371 | |
372 if (m_withTuningDifference) { | |
373 if (auto alignmentModel = | |
374 ModelById::getAs<AlignmentModel>(m_alignmentModel)) { | |
375 if (!done) { | |
376 int adjustedCompletion = 50 + completion/2; | |
377 if (adjustedCompletion > 99) { | |
378 adjustedCompletion = 99; | |
379 } | |
380 alignmentModel->setCompletion(adjustedCompletion); | |
381 } else { | |
382 alignmentModel->setCompletion(100); | |
383 } | |
384 } | |
385 } | |
386 | |
387 if (done) { | |
388 m_incomplete = false; | |
389 | |
390 ModelById::release(m_pathOutputModel); | |
391 m_pathOutputModel = {}; | |
392 | |
393 emit complete(m_alignmentModel); | |
394 } | |
395 } |