Chris@767
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@767
|
2
|
Chris@767
|
3 /*
|
Chris@767
|
4 Sonic Visualiser
|
Chris@767
|
5 An audio file viewer and annotation editor.
|
Chris@767
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@767
|
7
|
Chris@767
|
8 This program is free software; you can redistribute it and/or
|
Chris@767
|
9 modify it under the terms of the GNU General Public License as
|
Chris@767
|
10 published by the Free Software Foundation; either version 2 of the
|
Chris@767
|
11 License, or (at your option) any later version. See the file
|
Chris@767
|
12 COPYING included with this distribution for more information.
|
Chris@767
|
13 */
|
Chris@767
|
14
|
Chris@767
|
15 #include "TransformDTWAligner.h"
|
Chris@767
|
16 #include "DTW.h"
|
Chris@767
|
17
|
Chris@767
|
18 #include "data/model/SparseTimeValueModel.h"
|
Chris@767
|
19 #include "data/model/RangeSummarisableTimeValueModel.h"
|
Chris@767
|
20 #include "data/model/AlignmentModel.h"
|
Chris@767
|
21 #include "data/model/AggregateWaveModel.h"
|
Chris@767
|
22
|
Chris@767
|
23 #include "framework/Document.h"
|
Chris@767
|
24
|
Chris@767
|
25 #include "transform/ModelTransformerFactory.h"
|
Chris@767
|
26 #include "transform/FeatureExtractionModelTransformer.h"
|
Chris@767
|
27
|
Chris@767
|
28 #include <QSettings>
|
Chris@767
|
29 #include <QMutex>
|
Chris@767
|
30 #include <QMutexLocker>
|
Chris@767
|
31
|
Chris@767
|
32 using std::vector;
|
Chris@767
|
33
|
Chris@767
|
34 TransformDTWAligner::TransformDTWAligner(Document *doc,
|
Chris@767
|
35 ModelId reference,
|
Chris@767
|
36 ModelId toAlign,
|
Chris@767
|
37 Transform transform,
|
Chris@767
|
38 DTWType dtwType) :
|
Chris@767
|
39 m_document(doc),
|
Chris@767
|
40 m_reference(reference),
|
Chris@767
|
41 m_toAlign(toAlign),
|
Chris@767
|
42 m_referenceTransformComplete(false),
|
Chris@767
|
43 m_toAlignTransformComplete(false),
|
Chris@767
|
44 m_transform(transform),
|
Chris@767
|
45 m_dtwType(dtwType),
|
Chris@767
|
46 m_incomplete(true)
|
Chris@767
|
47 {
|
Chris@767
|
48 }
|
Chris@767
|
49
|
Chris@767
|
50 TransformDTWAligner::~TransformDTWAligner()
|
Chris@767
|
51 {
|
Chris@767
|
52 if (m_incomplete) {
|
Chris@767
|
53 if (auto toAlign = ModelById::get(m_toAlign)) {
|
Chris@767
|
54 toAlign->setAlignment({});
|
Chris@767
|
55 }
|
Chris@767
|
56 }
|
Chris@767
|
57
|
Chris@767
|
58 ModelById::release(m_referenceOutputModel);
|
Chris@767
|
59 ModelById::release(m_toAlignOutputModel);
|
Chris@767
|
60 ModelById::release(m_alignmentProgressModel);
|
Chris@767
|
61 }
|
Chris@767
|
62
|
Chris@767
|
63 bool
|
Chris@767
|
64 TransformDTWAligner::isAvailable()
|
Chris@767
|
65 {
|
Chris@767
|
66 //!!! needs to be isAvailable(QString transformId)?
|
Chris@767
|
67 return true;
|
Chris@767
|
68 }
|
Chris@767
|
69
|
Chris@767
|
70 void
|
Chris@767
|
71 TransformDTWAligner::begin()
|
Chris@767
|
72 {
|
Chris@767
|
73 auto reference =
|
Chris@767
|
74 ModelById::getAs<RangeSummarisableTimeValueModel>(m_reference);
|
Chris@767
|
75 auto toAlign =
|
Chris@767
|
76 ModelById::getAs<RangeSummarisableTimeValueModel>(m_toAlign);
|
Chris@767
|
77
|
Chris@767
|
78 if (!reference || !toAlign) return;
|
Chris@767
|
79
|
Chris@767
|
80 SVCERR << "TransformDTWAligner[" << this << "]: begin(): aligning "
|
Chris@767
|
81 << m_toAlign << " against reference " << m_reference << endl;
|
Chris@767
|
82
|
Chris@767
|
83 ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance();
|
Chris@767
|
84
|
Chris@767
|
85 QString message;
|
Chris@767
|
86
|
Chris@767
|
87 m_referenceOutputModel = mtf->transform(m_transform, m_reference, message);
|
Chris@767
|
88 auto referenceOutputModel = ModelById::get(m_referenceOutputModel);
|
Chris@767
|
89 if (!referenceOutputModel) {
|
Chris@767
|
90 SVCERR << "Align::alignModel: ERROR: Failed to create reference output model (no plugin?)" << endl;
|
Chris@767
|
91 emit failed(m_toAlign, message);
|
Chris@767
|
92 return;
|
Chris@767
|
93 }
|
Chris@767
|
94
|
Chris@767
|
95 SVCERR << "TransformDTWAligner[" << this << "]: begin(): transform id "
|
Chris@767
|
96 << m_transform.getIdentifier()
|
Chris@767
|
97 << " is running on reference model" << endl;
|
Chris@767
|
98
|
Chris@767
|
99 message = "";
|
Chris@767
|
100
|
Chris@767
|
101 m_toAlignOutputModel = mtf->transform(m_transform, m_toAlign, message);
|
Chris@767
|
102 auto toAlignOutputModel = ModelById::get(m_toAlignOutputModel);
|
Chris@767
|
103 if (!toAlignOutputModel) {
|
Chris@767
|
104 SVCERR << "Align::alignModel: ERROR: Failed to create toAlign output model (no plugin?)" << endl;
|
Chris@767
|
105 emit failed(m_toAlign, message);
|
Chris@767
|
106 return;
|
Chris@767
|
107 }
|
Chris@767
|
108
|
Chris@767
|
109 SVCERR << "TransformDTWAligner[" << this << "]: begin(): transform id "
|
Chris@767
|
110 << m_transform.getIdentifier()
|
Chris@767
|
111 << " is running on toAlign model" << endl;
|
Chris@767
|
112
|
Chris@767
|
113 connect(referenceOutputModel.get(), SIGNAL(completionChanged(ModelId)),
|
Chris@767
|
114 this, SLOT(completionChanged(ModelId)));
|
Chris@767
|
115 connect(toAlignOutputModel.get(), SIGNAL(completionChanged(ModelId)),
|
Chris@767
|
116 this, SLOT(completionChanged(ModelId)));
|
Chris@767
|
117
|
Chris@767
|
118 auto alignmentProgressModel = std::make_shared<SparseTimeValueModel>
|
Chris@767
|
119 (reference->getSampleRate(), m_transform.getStepSize(), false);
|
Chris@767
|
120 alignmentProgressModel->setCompletion(0);
|
Chris@767
|
121 m_alignmentProgressModel = ModelById::add(alignmentProgressModel);
|
Chris@767
|
122
|
Chris@767
|
123 auto alignmentModel = std::make_shared<AlignmentModel>
|
Chris@767
|
124 (m_reference, m_toAlign, m_alignmentProgressModel);
|
Chris@767
|
125 m_alignmentModel = ModelById::add(alignmentModel);
|
Chris@767
|
126
|
Chris@767
|
127 toAlign->setAlignment(m_alignmentModel);
|
Chris@767
|
128 m_document->addNonDerivedModel(m_alignmentModel);
|
Chris@767
|
129
|
Chris@767
|
130 // we wouldn't normally expect these to be true here, but...
|
Chris@767
|
131 int completion = 0;
|
Chris@767
|
132 if (referenceOutputModel->isReady(&completion) &&
|
Chris@767
|
133 toAlignOutputModel->isReady(&completion)) {
|
Chris@767
|
134 SVCERR << "TransformDTWAligner[" << this << "]: begin(): output models "
|
Chris@767
|
135 << "are ready already! calling performAlignment" << endl;
|
Chris@767
|
136 if (performAlignment()) {
|
Chris@767
|
137 emit complete(m_alignmentModel);
|
Chris@767
|
138 } else {
|
Chris@767
|
139 emit failed(m_toAlign, tr("Failed to calculate alignment using DTW"));
|
Chris@767
|
140 }
|
Chris@767
|
141 }
|
Chris@767
|
142 }
|
Chris@767
|
143
|
Chris@767
|
144 void
|
Chris@767
|
145 TransformDTWAligner::completionChanged(ModelId id)
|
Chris@767
|
146 {
|
Chris@767
|
147 if (!m_incomplete) {
|
Chris@767
|
148 return;
|
Chris@767
|
149 }
|
Chris@767
|
150
|
Chris@767
|
151 SVCERR << "TransformDTWAligner[" << this << "]: completionChanged: "
|
Chris@767
|
152 << "model " << id << endl;
|
Chris@767
|
153
|
Chris@767
|
154 auto referenceOutputModel = ModelById::get(m_referenceOutputModel);
|
Chris@767
|
155 auto toAlignOutputModel = ModelById::get(m_toAlignOutputModel);
|
Chris@767
|
156
|
Chris@767
|
157 if (!referenceOutputModel || !toAlignOutputModel) {
|
Chris@767
|
158 return;
|
Chris@767
|
159 }
|
Chris@767
|
160
|
Chris@767
|
161 int referenceCompletion = 0, toAlignCompletion = 0;
|
Chris@767
|
162 bool referenceReady = referenceOutputModel->isReady(&referenceCompletion);
|
Chris@767
|
163 bool toAlignReady = toAlignOutputModel->isReady(&toAlignCompletion);
|
Chris@767
|
164
|
Chris@767
|
165 auto alignmentProgressModel =
|
Chris@767
|
166 ModelById::getAs<SparseTimeValueModel>(m_alignmentProgressModel);
|
Chris@767
|
167
|
Chris@767
|
168 if (referenceReady && toAlignReady) {
|
Chris@767
|
169
|
Chris@767
|
170 SVCERR << "TransformDTWAligner[" << this << "]: completionChanged: "
|
Chris@767
|
171 << "ready, calling performAlignment" << endl;
|
Chris@767
|
172
|
Chris@767
|
173 if (alignmentProgressModel) {
|
Chris@767
|
174 alignmentProgressModel->setCompletion(95);
|
Chris@767
|
175 }
|
Chris@767
|
176
|
Chris@767
|
177 if (performAlignment()) {
|
Chris@767
|
178 emit complete(m_alignmentModel);
|
Chris@767
|
179 } else {
|
Chris@767
|
180 emit failed(m_toAlign, tr("Alignment of transform outputs failed"));
|
Chris@767
|
181 }
|
Chris@767
|
182
|
Chris@767
|
183 } else {
|
Chris@767
|
184
|
Chris@767
|
185 SVCERR << "TransformDTWAligner[" << this << "]: completionChanged: "
|
Chris@767
|
186 << "not ready yet: reference completion " << referenceCompletion
|
Chris@767
|
187 << ", toAlign completion " << toAlignCompletion << endl;
|
Chris@767
|
188
|
Chris@767
|
189 if (alignmentProgressModel) {
|
Chris@767
|
190 int completion = std::min(referenceCompletion,
|
Chris@767
|
191 toAlignCompletion);
|
Chris@767
|
192 completion = (completion * 94) / 100;
|
Chris@767
|
193 alignmentProgressModel->setCompletion(completion);
|
Chris@767
|
194 }
|
Chris@767
|
195 }
|
Chris@767
|
196 }
|
Chris@767
|
197
|
Chris@767
|
198 bool
|
Chris@767
|
199 TransformDTWAligner::performAlignment()
|
Chris@767
|
200 {
|
Chris@767
|
201 if (m_dtwType == Magnitude) {
|
Chris@767
|
202 return performAlignmentMagnitude();
|
Chris@767
|
203 } else {
|
Chris@767
|
204 return performAlignmentRiseFall();
|
Chris@767
|
205 }
|
Chris@767
|
206 }
|
Chris@767
|
207
|
Chris@767
|
208 bool
|
Chris@767
|
209 TransformDTWAligner::performAlignmentMagnitude()
|
Chris@767
|
210 {
|
Chris@767
|
211 auto referenceOutputSTVM = ModelById::getAs<SparseTimeValueModel>
|
Chris@767
|
212 (m_referenceOutputModel);
|
Chris@767
|
213 auto toAlignOutputSTVM = ModelById::getAs<SparseTimeValueModel>
|
Chris@767
|
214 (m_toAlignOutputModel);
|
Chris@767
|
215 auto alignmentModel = ModelById::getAs<AlignmentModel>
|
Chris@767
|
216 (m_alignmentModel);
|
Chris@767
|
217
|
Chris@767
|
218 if (!referenceOutputSTVM || !toAlignOutputSTVM) {
|
Chris@767
|
219 //!!! what?
|
Chris@767
|
220 return false;
|
Chris@767
|
221 }
|
Chris@767
|
222
|
Chris@767
|
223 if (!alignmentModel) {
|
Chris@767
|
224 return false;
|
Chris@767
|
225 }
|
Chris@767
|
226
|
Chris@767
|
227 vector<double> s1, s2;
|
Chris@767
|
228
|
Chris@767
|
229 {
|
Chris@767
|
230 auto events = referenceOutputSTVM->getAllEvents();
|
Chris@767
|
231 for (auto e: events) {
|
Chris@767
|
232 s1.push_back(e.getValue());
|
Chris@767
|
233 }
|
Chris@767
|
234 events = toAlignOutputSTVM->getAllEvents();
|
Chris@767
|
235 for (auto e: events) {
|
Chris@767
|
236 s2.push_back(e.getValue());
|
Chris@767
|
237 }
|
Chris@767
|
238 }
|
Chris@767
|
239
|
Chris@767
|
240 SVCERR << "TransformDTWAligner[" << this << "]: performAlignment: "
|
Chris@767
|
241 << "Have " << s1.size() << " events from reference, "
|
Chris@767
|
242 << s2.size() << " from toAlign" << endl;
|
Chris@767
|
243
|
Chris@767
|
244 MagnitudeDTW dtw;
|
Chris@767
|
245 vector<size_t> alignment;
|
Chris@767
|
246
|
Chris@767
|
247 {
|
Chris@767
|
248 SVCERR << "TransformDTWAligner[" << this
|
Chris@767
|
249 << "]: serialising DTW to avoid over-allocation" << endl;
|
Chris@767
|
250 static QMutex mutex;
|
Chris@767
|
251 QMutexLocker locker(&mutex);
|
Chris@767
|
252
|
Chris@767
|
253 alignment = dtw.alignSeries(s1, s2);
|
Chris@767
|
254 }
|
Chris@767
|
255
|
Chris@767
|
256 SVCERR << "TransformDTWAligner[" << this << "]: performAlignment: "
|
Chris@767
|
257 << "DTW produced " << alignment.size() << " points:" << endl;
|
Chris@767
|
258 for (int i = 0; i < alignment.size() && i < 100; ++i) {
|
Chris@767
|
259 SVCERR << alignment[i] << " ";
|
Chris@767
|
260 }
|
Chris@767
|
261 SVCERR << endl;
|
Chris@767
|
262
|
Chris@767
|
263 auto alignmentProgressModel =
|
Chris@767
|
264 ModelById::getAs<SparseTimeValueModel>(m_alignmentProgressModel);
|
Chris@767
|
265 if (alignmentProgressModel) {
|
Chris@767
|
266 alignmentProgressModel->setCompletion(100);
|
Chris@767
|
267 }
|
Chris@767
|
268
|
Chris@767
|
269 // clear the alignment progress model
|
Chris@767
|
270 alignmentModel->setPathFrom(ModelId());
|
Chris@767
|
271
|
Chris@767
|
272 sv_frame_t resolution = referenceOutputSTVM->getResolution();
|
Chris@767
|
273 sv_frame_t sourceFrame = 0;
|
Chris@767
|
274
|
Chris@767
|
275 Path path(referenceOutputSTVM->getSampleRate(), resolution);
|
Chris@767
|
276
|
Chris@767
|
277 for (size_t m: alignment) {
|
Chris@767
|
278 path.add(PathPoint(sourceFrame, sv_frame_t(m) * resolution));
|
Chris@767
|
279 sourceFrame += resolution;
|
Chris@767
|
280 }
|
Chris@767
|
281
|
Chris@767
|
282 alignmentModel->setPath(path);
|
Chris@767
|
283
|
Chris@767
|
284 SVCERR << "TransformDTWAligner[" << this << "]: performAlignment: Done"
|
Chris@767
|
285 << endl;
|
Chris@767
|
286
|
Chris@767
|
287 m_incomplete = false;
|
Chris@767
|
288 return true;
|
Chris@767
|
289 }
|
Chris@767
|
290
|
Chris@767
|
291 bool
|
Chris@767
|
292 TransformDTWAligner::performAlignmentRiseFall()
|
Chris@767
|
293 {
|
Chris@767
|
294 auto referenceOutputSTVM = ModelById::getAs<SparseTimeValueModel>
|
Chris@767
|
295 (m_referenceOutputModel);
|
Chris@767
|
296 auto toAlignOutputSTVM = ModelById::getAs<SparseTimeValueModel>
|
Chris@767
|
297 (m_toAlignOutputModel);
|
Chris@767
|
298 auto alignmentModel = ModelById::getAs<AlignmentModel>
|
Chris@767
|
299 (m_alignmentModel);
|
Chris@767
|
300
|
Chris@767
|
301 if (!referenceOutputSTVM || !toAlignOutputSTVM) {
|
Chris@767
|
302 //!!! what?
|
Chris@767
|
303 return false;
|
Chris@767
|
304 }
|
Chris@767
|
305
|
Chris@767
|
306 if (!alignmentModel) {
|
Chris@767
|
307 return false;
|
Chris@767
|
308 }
|
Chris@767
|
309
|
Chris@767
|
310 vector<RiseFallDTW::Value> s1, s2;
|
Chris@767
|
311 double prev1 = 0.0, prev2 = 0.0;
|
Chris@767
|
312
|
Chris@767
|
313 {
|
Chris@767
|
314 auto events = referenceOutputSTVM->getAllEvents();
|
Chris@767
|
315 for (auto e: events) {
|
Chris@767
|
316 double v = e.getValue();
|
Chris@767
|
317 //!!! the original does this using MIDI pitch for the
|
Chris@767
|
318 //!!! pYin transform... rework with a lambda passed in
|
Chris@767
|
319 //!!! for modification maybe? + factor out s1/s2 of course
|
Chris@767
|
320 if (v > prev1) {
|
Chris@767
|
321 s1.push_back({ RiseFallDTW::Direction::Up, v - prev1 });
|
Chris@767
|
322 } else {
|
Chris@767
|
323 s1.push_back({ RiseFallDTW::Direction::Down, prev1 - v });
|
Chris@767
|
324 }
|
Chris@767
|
325 prev1 = v;
|
Chris@767
|
326 }
|
Chris@767
|
327 events = toAlignOutputSTVM->getAllEvents();
|
Chris@767
|
328 for (auto e: events) {
|
Chris@767
|
329 double v = e.getValue();
|
Chris@767
|
330 //!!! as above
|
Chris@767
|
331 if (v > prev2) {
|
Chris@767
|
332 s2.push_back({ RiseFallDTW::Direction::Up, v - prev2 });
|
Chris@767
|
333 } else {
|
Chris@767
|
334 s2.push_back({ RiseFallDTW::Direction::Down, prev2 - v });
|
Chris@767
|
335 }
|
Chris@767
|
336 prev2 = v;
|
Chris@767
|
337 }
|
Chris@767
|
338 }
|
Chris@767
|
339
|
Chris@767
|
340 SVCERR << "TransformDTWAligner[" << this << "]: performAlignment: "
|
Chris@767
|
341 << "Have " << s1.size() << " events from reference, "
|
Chris@767
|
342 << s2.size() << " from toAlign" << endl;
|
Chris@767
|
343
|
Chris@767
|
344 RiseFallDTW dtw;
|
Chris@767
|
345
|
Chris@767
|
346 vector<size_t> alignment;
|
Chris@767
|
347
|
Chris@767
|
348 {
|
Chris@767
|
349 SVCERR << "TransformDTWAligner[" << this
|
Chris@767
|
350 << "]: serialising DTW to avoid over-allocation" << endl;
|
Chris@767
|
351 static QMutex mutex;
|
Chris@767
|
352 QMutexLocker locker(&mutex);
|
Chris@767
|
353
|
Chris@767
|
354 alignment = dtw.alignSeries(s1, s2);
|
Chris@767
|
355 }
|
Chris@767
|
356
|
Chris@767
|
357 SVCERR << "TransformDTWAligner[" << this << "]: performAlignment: "
|
Chris@767
|
358 << "DTW produced " << alignment.size() << " points:" << endl;
|
Chris@767
|
359 for (int i = 0; i < alignment.size() && i < 100; ++i) {
|
Chris@767
|
360 SVCERR << alignment[i] << " ";
|
Chris@767
|
361 }
|
Chris@767
|
362 SVCERR << endl;
|
Chris@767
|
363
|
Chris@767
|
364 auto alignmentProgressModel =
|
Chris@767
|
365 ModelById::getAs<SparseTimeValueModel>(m_alignmentProgressModel);
|
Chris@767
|
366 if (alignmentProgressModel) {
|
Chris@767
|
367 alignmentProgressModel->setCompletion(100);
|
Chris@767
|
368 }
|
Chris@767
|
369
|
Chris@767
|
370 // clear the alignment progress model
|
Chris@767
|
371 alignmentModel->setPathFrom(ModelId());
|
Chris@767
|
372
|
Chris@767
|
373 sv_frame_t resolution = referenceOutputSTVM->getResolution();
|
Chris@767
|
374 sv_frame_t sourceFrame = 0;
|
Chris@767
|
375
|
Chris@767
|
376 Path path(referenceOutputSTVM->getSampleRate(), resolution);
|
Chris@767
|
377
|
Chris@767
|
378 for (size_t m: alignment) {
|
Chris@767
|
379 path.add(PathPoint(sourceFrame, sv_frame_t(m) * resolution));
|
Chris@767
|
380 sourceFrame += resolution;
|
Chris@767
|
381 }
|
Chris@767
|
382
|
Chris@767
|
383 alignmentModel->setPath(path);
|
Chris@767
|
384
|
Chris@767
|
385 SVCERR << "TransformDTWAligner[" << this << "]: performAlignment: Done"
|
Chris@767
|
386 << endl;
|
Chris@767
|
387
|
Chris@767
|
388 m_incomplete = false;
|
Chris@767
|
389 return true;
|
Chris@767
|
390 }
|