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