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_transform(transform),
|
Chris@767
|
43 m_dtwType(dtwType),
|
Chris@768
|
44 m_incomplete(true),
|
Chris@768
|
45 m_outputPreprocessor([](double x) { return x; })
|
Chris@768
|
46 {
|
Chris@768
|
47 }
|
Chris@768
|
48
|
Chris@768
|
49 TransformDTWAligner::TransformDTWAligner(Document *doc,
|
Chris@768
|
50 ModelId reference,
|
Chris@768
|
51 ModelId toAlign,
|
Chris@768
|
52 Transform transform,
|
Chris@768
|
53 DTWType dtwType,
|
Chris@768
|
54 std::function<double(double)>
|
Chris@768
|
55 outputPreprocessor) :
|
Chris@768
|
56 m_document(doc),
|
Chris@768
|
57 m_reference(reference),
|
Chris@768
|
58 m_toAlign(toAlign),
|
Chris@768
|
59 m_transform(transform),
|
Chris@768
|
60 m_dtwType(dtwType),
|
Chris@768
|
61 m_incomplete(true),
|
Chris@768
|
62 m_outputPreprocessor(outputPreprocessor)
|
Chris@767
|
63 {
|
Chris@767
|
64 }
|
Chris@767
|
65
|
Chris@767
|
66 TransformDTWAligner::~TransformDTWAligner()
|
Chris@767
|
67 {
|
Chris@767
|
68 if (m_incomplete) {
|
Chris@767
|
69 if (auto toAlign = ModelById::get(m_toAlign)) {
|
Chris@767
|
70 toAlign->setAlignment({});
|
Chris@767
|
71 }
|
Chris@767
|
72 }
|
Chris@767
|
73
|
Chris@767
|
74 ModelById::release(m_referenceOutputModel);
|
Chris@767
|
75 ModelById::release(m_toAlignOutputModel);
|
Chris@767
|
76 }
|
Chris@767
|
77
|
Chris@767
|
78 bool
|
Chris@767
|
79 TransformDTWAligner::isAvailable()
|
Chris@767
|
80 {
|
Chris@767
|
81 //!!! needs to be isAvailable(QString transformId)?
|
Chris@767
|
82 return true;
|
Chris@767
|
83 }
|
Chris@767
|
84
|
Chris@767
|
85 void
|
Chris@767
|
86 TransformDTWAligner::begin()
|
Chris@767
|
87 {
|
Chris@767
|
88 auto reference =
|
Chris@767
|
89 ModelById::getAs<RangeSummarisableTimeValueModel>(m_reference);
|
Chris@767
|
90 auto toAlign =
|
Chris@767
|
91 ModelById::getAs<RangeSummarisableTimeValueModel>(m_toAlign);
|
Chris@767
|
92
|
Chris@767
|
93 if (!reference || !toAlign) return;
|
Chris@767
|
94
|
Chris@767
|
95 SVCERR << "TransformDTWAligner[" << this << "]: begin(): aligning "
|
Chris@767
|
96 << m_toAlign << " against reference " << m_reference << endl;
|
Chris@767
|
97
|
Chris@767
|
98 ModelTransformerFactory *mtf = ModelTransformerFactory::getInstance();
|
Chris@767
|
99
|
Chris@767
|
100 QString message;
|
Chris@767
|
101
|
Chris@767
|
102 m_referenceOutputModel = mtf->transform(m_transform, m_reference, message);
|
Chris@767
|
103 auto referenceOutputModel = ModelById::get(m_referenceOutputModel);
|
Chris@767
|
104 if (!referenceOutputModel) {
|
Chris@767
|
105 SVCERR << "Align::alignModel: ERROR: Failed to create reference output model (no plugin?)" << endl;
|
Chris@767
|
106 emit failed(m_toAlign, message);
|
Chris@767
|
107 return;
|
Chris@767
|
108 }
|
Chris@767
|
109
|
Chris@767
|
110 SVCERR << "TransformDTWAligner[" << this << "]: begin(): transform id "
|
Chris@767
|
111 << m_transform.getIdentifier()
|
Chris@767
|
112 << " is running on reference model" << endl;
|
Chris@767
|
113
|
Chris@767
|
114 message = "";
|
Chris@767
|
115
|
Chris@767
|
116 m_toAlignOutputModel = mtf->transform(m_transform, m_toAlign, message);
|
Chris@767
|
117 auto toAlignOutputModel = ModelById::get(m_toAlignOutputModel);
|
Chris@767
|
118 if (!toAlignOutputModel) {
|
Chris@767
|
119 SVCERR << "Align::alignModel: ERROR: Failed to create toAlign output model (no plugin?)" << endl;
|
Chris@767
|
120 emit failed(m_toAlign, message);
|
Chris@767
|
121 return;
|
Chris@767
|
122 }
|
Chris@767
|
123
|
Chris@767
|
124 SVCERR << "TransformDTWAligner[" << this << "]: begin(): transform id "
|
Chris@767
|
125 << m_transform.getIdentifier()
|
Chris@767
|
126 << " is running on toAlign model" << endl;
|
Chris@767
|
127
|
Chris@767
|
128 connect(referenceOutputModel.get(), SIGNAL(completionChanged(ModelId)),
|
Chris@767
|
129 this, SLOT(completionChanged(ModelId)));
|
Chris@767
|
130 connect(toAlignOutputModel.get(), SIGNAL(completionChanged(ModelId)),
|
Chris@767
|
131 this, SLOT(completionChanged(ModelId)));
|
Chris@767
|
132
|
Chris@767
|
133 auto alignmentModel = std::make_shared<AlignmentModel>
|
Chris@768
|
134 (m_reference, m_toAlign, ModelId());
|
Chris@767
|
135 m_alignmentModel = ModelById::add(alignmentModel);
|
Chris@767
|
136
|
Chris@767
|
137 toAlign->setAlignment(m_alignmentModel);
|
Chris@767
|
138 m_document->addNonDerivedModel(m_alignmentModel);
|
Chris@767
|
139
|
Chris@767
|
140 // we wouldn't normally expect these to be true here, but...
|
Chris@767
|
141 int completion = 0;
|
Chris@767
|
142 if (referenceOutputModel->isReady(&completion) &&
|
Chris@767
|
143 toAlignOutputModel->isReady(&completion)) {
|
Chris@767
|
144 SVCERR << "TransformDTWAligner[" << this << "]: begin(): output models "
|
Chris@767
|
145 << "are ready already! calling performAlignment" << endl;
|
Chris@767
|
146 if (performAlignment()) {
|
Chris@767
|
147 emit complete(m_alignmentModel);
|
Chris@767
|
148 } else {
|
Chris@767
|
149 emit failed(m_toAlign, tr("Failed to calculate alignment using DTW"));
|
Chris@767
|
150 }
|
Chris@767
|
151 }
|
Chris@767
|
152 }
|
Chris@767
|
153
|
Chris@767
|
154 void
|
Chris@767
|
155 TransformDTWAligner::completionChanged(ModelId id)
|
Chris@767
|
156 {
|
Chris@767
|
157 if (!m_incomplete) {
|
Chris@767
|
158 return;
|
Chris@767
|
159 }
|
Chris@767
|
160
|
Chris@767
|
161 SVCERR << "TransformDTWAligner[" << this << "]: completionChanged: "
|
Chris@767
|
162 << "model " << id << endl;
|
Chris@767
|
163
|
Chris@767
|
164 auto referenceOutputModel = ModelById::get(m_referenceOutputModel);
|
Chris@767
|
165 auto toAlignOutputModel = ModelById::get(m_toAlignOutputModel);
|
Chris@768
|
166 auto alignmentModel = ModelById::getAs<AlignmentModel>(m_alignmentModel);
|
Chris@767
|
167
|
Chris@768
|
168 if (!referenceOutputModel || !toAlignOutputModel || !alignmentModel) {
|
Chris@767
|
169 return;
|
Chris@767
|
170 }
|
Chris@767
|
171
|
Chris@767
|
172 int referenceCompletion = 0, toAlignCompletion = 0;
|
Chris@767
|
173 bool referenceReady = referenceOutputModel->isReady(&referenceCompletion);
|
Chris@767
|
174 bool toAlignReady = toAlignOutputModel->isReady(&toAlignCompletion);
|
Chris@767
|
175
|
Chris@767
|
176 if (referenceReady && toAlignReady) {
|
Chris@767
|
177
|
Chris@767
|
178 SVCERR << "TransformDTWAligner[" << this << "]: completionChanged: "
|
Chris@767
|
179 << "ready, calling performAlignment" << endl;
|
Chris@767
|
180
|
Chris@768
|
181 alignmentModel->setCompletion(95);
|
Chris@767
|
182
|
Chris@767
|
183 if (performAlignment()) {
|
Chris@767
|
184 emit complete(m_alignmentModel);
|
Chris@767
|
185 } else {
|
Chris@767
|
186 emit failed(m_toAlign, tr("Alignment of transform outputs failed"));
|
Chris@767
|
187 }
|
Chris@767
|
188
|
Chris@767
|
189 } else {
|
Chris@767
|
190
|
Chris@767
|
191 SVCERR << "TransformDTWAligner[" << this << "]: completionChanged: "
|
Chris@767
|
192 << "not ready yet: reference completion " << referenceCompletion
|
Chris@767
|
193 << ", toAlign completion " << toAlignCompletion << endl;
|
Chris@767
|
194
|
Chris@768
|
195 int completion = std::min(referenceCompletion,
|
Chris@768
|
196 toAlignCompletion);
|
Chris@768
|
197 completion = (completion * 94) / 100;
|
Chris@768
|
198 alignmentModel->setCompletion(completion);
|
Chris@767
|
199 }
|
Chris@767
|
200 }
|
Chris@767
|
201
|
Chris@767
|
202 bool
|
Chris@767
|
203 TransformDTWAligner::performAlignment()
|
Chris@767
|
204 {
|
Chris@767
|
205 if (m_dtwType == Magnitude) {
|
Chris@767
|
206 return performAlignmentMagnitude();
|
Chris@767
|
207 } else {
|
Chris@767
|
208 return performAlignmentRiseFall();
|
Chris@767
|
209 }
|
Chris@767
|
210 }
|
Chris@767
|
211
|
Chris@767
|
212 bool
|
Chris@767
|
213 TransformDTWAligner::performAlignmentMagnitude()
|
Chris@767
|
214 {
|
Chris@767
|
215 auto referenceOutputSTVM = ModelById::getAs<SparseTimeValueModel>
|
Chris@767
|
216 (m_referenceOutputModel);
|
Chris@767
|
217 auto toAlignOutputSTVM = ModelById::getAs<SparseTimeValueModel>
|
Chris@767
|
218 (m_toAlignOutputModel);
|
Chris@767
|
219 auto alignmentModel = ModelById::getAs<AlignmentModel>
|
Chris@767
|
220 (m_alignmentModel);
|
Chris@767
|
221
|
Chris@767
|
222 if (!referenceOutputSTVM || !toAlignOutputSTVM) {
|
Chris@767
|
223 //!!! what?
|
Chris@767
|
224 return false;
|
Chris@767
|
225 }
|
Chris@767
|
226
|
Chris@767
|
227 if (!alignmentModel) {
|
Chris@767
|
228 return false;
|
Chris@767
|
229 }
|
Chris@767
|
230
|
Chris@767
|
231 vector<double> s1, s2;
|
Chris@767
|
232
|
Chris@767
|
233 {
|
Chris@767
|
234 auto events = referenceOutputSTVM->getAllEvents();
|
Chris@767
|
235 for (auto e: events) {
|
Chris@768
|
236 s1.push_back(m_outputPreprocessor(e.getValue()));
|
Chris@767
|
237 }
|
Chris@767
|
238 events = toAlignOutputSTVM->getAllEvents();
|
Chris@767
|
239 for (auto e: events) {
|
Chris@768
|
240 s2.push_back(m_outputPreprocessor(e.getValue()));
|
Chris@767
|
241 }
|
Chris@767
|
242 }
|
Chris@767
|
243
|
Chris@769
|
244 SVCERR << "TransformDTWAligner[" << this << "]: performAlignmentMagnitude: "
|
Chris@767
|
245 << "Have " << s1.size() << " events from reference, "
|
Chris@767
|
246 << s2.size() << " from toAlign" << endl;
|
Chris@767
|
247
|
Chris@767
|
248 MagnitudeDTW dtw;
|
Chris@767
|
249 vector<size_t> alignment;
|
Chris@767
|
250
|
Chris@767
|
251 {
|
Chris@767
|
252 SVCERR << "TransformDTWAligner[" << this
|
Chris@767
|
253 << "]: serialising DTW to avoid over-allocation" << endl;
|
Chris@767
|
254 static QMutex mutex;
|
Chris@767
|
255 QMutexLocker locker(&mutex);
|
Chris@767
|
256
|
Chris@767
|
257 alignment = dtw.alignSeries(s1, s2);
|
Chris@767
|
258 }
|
Chris@767
|
259
|
Chris@769
|
260 SVCERR << "TransformDTWAligner[" << this << "]: performAlignmentMagnitude: "
|
Chris@767
|
261 << "DTW produced " << alignment.size() << " points:" << endl;
|
Chris@767
|
262 for (int i = 0; i < alignment.size() && i < 100; ++i) {
|
Chris@767
|
263 SVCERR << alignment[i] << " ";
|
Chris@767
|
264 }
|
Chris@767
|
265 SVCERR << endl;
|
Chris@767
|
266
|
Chris@768
|
267 alignmentModel->setCompletion(100);
|
Chris@767
|
268
|
Chris@767
|
269 sv_frame_t resolution = referenceOutputSTVM->getResolution();
|
Chris@767
|
270 sv_frame_t sourceFrame = 0;
|
Chris@767
|
271
|
Chris@767
|
272 Path path(referenceOutputSTVM->getSampleRate(), resolution);
|
Chris@767
|
273
|
Chris@767
|
274 for (size_t m: alignment) {
|
Chris@767
|
275 path.add(PathPoint(sourceFrame, sv_frame_t(m) * resolution));
|
Chris@767
|
276 sourceFrame += resolution;
|
Chris@767
|
277 }
|
Chris@767
|
278
|
Chris@767
|
279 alignmentModel->setPath(path);
|
Chris@767
|
280
|
Chris@769
|
281 SVCERR << "TransformDTWAligner[" << this << "]: performAlignmentMagnitude: Done"
|
Chris@767
|
282 << endl;
|
Chris@767
|
283
|
Chris@767
|
284 m_incomplete = false;
|
Chris@767
|
285 return true;
|
Chris@767
|
286 }
|
Chris@767
|
287
|
Chris@767
|
288 bool
|
Chris@767
|
289 TransformDTWAligner::performAlignmentRiseFall()
|
Chris@767
|
290 {
|
Chris@767
|
291 auto referenceOutputSTVM = ModelById::getAs<SparseTimeValueModel>
|
Chris@767
|
292 (m_referenceOutputModel);
|
Chris@767
|
293 auto toAlignOutputSTVM = ModelById::getAs<SparseTimeValueModel>
|
Chris@767
|
294 (m_toAlignOutputModel);
|
Chris@767
|
295 auto alignmentModel = ModelById::getAs<AlignmentModel>
|
Chris@767
|
296 (m_alignmentModel);
|
Chris@767
|
297
|
Chris@767
|
298 if (!referenceOutputSTVM || !toAlignOutputSTVM) {
|
Chris@767
|
299 //!!! what?
|
Chris@767
|
300 return false;
|
Chris@767
|
301 }
|
Chris@767
|
302
|
Chris@767
|
303 if (!alignmentModel) {
|
Chris@767
|
304 return false;
|
Chris@767
|
305 }
|
Chris@768
|
306
|
Chris@768
|
307 auto convertEvents =
|
Chris@768
|
308 [this](const EventVector &ee) {
|
Chris@768
|
309 vector<RiseFallDTW::Value> s;
|
Chris@768
|
310 double prev = 0.0;
|
Chris@768
|
311 for (auto e: ee) {
|
Chris@768
|
312 double v = m_outputPreprocessor(e.getValue());
|
Chris@768
|
313 if (v == prev || s.empty()) {
|
Chris@768
|
314 s.push_back({ RiseFallDTW::Direction::None, 0.0 });
|
Chris@768
|
315 } else if (v > prev) {
|
Chris@768
|
316 s.push_back({ RiseFallDTW::Direction::Up, v - prev });
|
Chris@768
|
317 } else {
|
Chris@768
|
318 s.push_back({ RiseFallDTW::Direction::Down, prev - v });
|
Chris@768
|
319 }
|
Chris@768
|
320 }
|
Chris@768
|
321 return s;
|
Chris@768
|
322 };
|
Chris@767
|
323
|
Chris@768
|
324 vector<RiseFallDTW::Value> s1 =
|
Chris@768
|
325 convertEvents(referenceOutputSTVM->getAllEvents());
|
Chris@767
|
326
|
Chris@768
|
327 vector<RiseFallDTW::Value> s2 =
|
Chris@768
|
328 convertEvents(toAlignOutputSTVM->getAllEvents());
|
Chris@767
|
329
|
Chris@769
|
330 SVCERR << "TransformDTWAligner[" << this << "]: performAlignmentRiseFall: "
|
Chris@767
|
331 << "Have " << s1.size() << " events from reference, "
|
Chris@767
|
332 << s2.size() << " from toAlign" << endl;
|
Chris@767
|
333
|
Chris@767
|
334 RiseFallDTW dtw;
|
Chris@767
|
335
|
Chris@767
|
336 vector<size_t> alignment;
|
Chris@767
|
337
|
Chris@767
|
338 {
|
Chris@767
|
339 SVCERR << "TransformDTWAligner[" << this
|
Chris@767
|
340 << "]: serialising DTW to avoid over-allocation" << endl;
|
Chris@767
|
341 static QMutex mutex;
|
Chris@767
|
342 QMutexLocker locker(&mutex);
|
Chris@767
|
343
|
Chris@767
|
344 alignment = dtw.alignSeries(s1, s2);
|
Chris@767
|
345 }
|
Chris@767
|
346
|
Chris@769
|
347 SVCERR << "TransformDTWAligner[" << this << "]: performAlignmentRiseFall: "
|
Chris@767
|
348 << "DTW produced " << alignment.size() << " points:" << endl;
|
Chris@767
|
349 for (int i = 0; i < alignment.size() && i < 100; ++i) {
|
Chris@767
|
350 SVCERR << alignment[i] << " ";
|
Chris@767
|
351 }
|
Chris@767
|
352 SVCERR << endl;
|
Chris@767
|
353
|
Chris@768
|
354 alignmentModel->setCompletion(100);
|
Chris@767
|
355
|
Chris@767
|
356 sv_frame_t resolution = referenceOutputSTVM->getResolution();
|
Chris@767
|
357 sv_frame_t sourceFrame = 0;
|
Chris@767
|
358
|
Chris@767
|
359 Path path(referenceOutputSTVM->getSampleRate(), resolution);
|
Chris@767
|
360
|
Chris@767
|
361 for (size_t m: alignment) {
|
Chris@767
|
362 path.add(PathPoint(sourceFrame, sv_frame_t(m) * resolution));
|
Chris@767
|
363 sourceFrame += resolution;
|
Chris@767
|
364 }
|
Chris@767
|
365
|
Chris@767
|
366 alignmentModel->setPath(path);
|
Chris@767
|
367
|
Chris@769
|
368 SVCERR << "TransformDTWAligner[" << this << "]: performAlignmentRiseFall: Done"
|
Chris@767
|
369 << endl;
|
Chris@767
|
370
|
Chris@767
|
371 m_incomplete = false;
|
Chris@767
|
372 return true;
|
Chris@767
|
373 }
|