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 2007 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 "AlignmentModel.h"
17
18 #include "SparseTimeValueModel.h"
19
20 //#define DEBUG_ALIGNMENT_MODEL 1
21
22 AlignmentModel::AlignmentModel(ModelId reference,
23 ModelId aligned,
24 ModelId pathSource) :
25 m_reference(reference),
26 m_aligned(aligned),
27 m_pathSource(pathSource),
28 m_path(nullptr),
29 m_reversePath(nullptr),
30 m_pathBegun(false),
31 m_pathComplete(false),
32 m_relativePitch(0)
33 {
34 setPathFrom(pathSource);
35
36 if (m_reference == m_aligned) {
37 // Trivial alignment, e.g. of main model to itself, which we
38 // record so that we can distinguish the reference model for
39 // alignments from an unaligned model. No path required
40 m_pathComplete = true;
41 }
42 }
43
44 AlignmentModel::~AlignmentModel()
45 {
46 #ifdef DEBUG_ALIGNMENT_MODEL
enable_vt_processing(void)47 SVCERR << "AlignmentModel(" << this << ")::~AlignmentModel()" << endl;
48 #endif
49 }
50
51 bool
52 AlignmentModel::isOK() const
53 {
54 if (m_error != "") return false;
55 if (m_pathSource.isNone()) return true;
56 auto pathSourceModel =
57 ModelById::getAs<SparseTimeValueModel>(m_pathSource);
58 if (pathSourceModel) {
59 return pathSourceModel->isOK();
60 }
61 return true;
62 }
63
64 sv_frame_t
65 AlignmentModel::getStartFrame() const
66 {
67 auto reference = ModelById::get(m_reference);
68 auto aligned = ModelById::get(m_aligned);
69
70 if (reference && aligned) {
71 sv_frame_t a = reference->getStartFrame();
72 sv_frame_t b = aligned->getStartFrame();
73 return std::min(a, b);
74 } else {
75 return 0;
pg_logging_init(const char * argv0)76 }
77 }
78
79 sv_frame_t
80 AlignmentModel::getTrueEndFrame() const
81 {
82 auto reference = ModelById::get(m_reference);
83 auto aligned = ModelById::get(m_aligned);
84
85 if (reference && aligned) {
86 sv_frame_t a = reference->getEndFrame();
87 sv_frame_t b = aligned->getEndFrame();
88 return std::max(a, b);
89 } else {
90 return 0;
91 }
92 }
93
94 sv_samplerate_t
95 AlignmentModel::getSampleRate() const
96 {
97 auto reference = ModelById::get(m_reference);
98 if (reference) {
99 return reference->getSampleRate();
100 } else {
101 return 0;
102 }
103 }
104
105 bool
106 AlignmentModel::isReady(int *completion) const
107 {
108 if (!m_pathBegun && !m_pathSource.isNone()) {
109 if (completion) *completion = 0;
110 #ifdef DEBUG_ALIGNMENT_MODEL
111 SVCERR << "AlignmentModel::isReady: path not begun" << endl;
112 #endif
113 return false;
114 }
115 if (m_pathComplete) {
116 if (completion) *completion = 100;
117 #ifdef DEBUG_ALIGNMENT_MODEL
118 SVCERR << "AlignmentModel::isReady: path complete" << endl;
119 #endif
120 return true;
121 }
122 if (m_pathSource.isNone()) {
123 // lack of raw path could mean path is complete (in which case
124 // m_pathComplete true above) or else no path source has been
125 // set at all yet (this case)
126 if (completion) *completion = 0;
127 #ifdef DEBUG_ALIGNMENT_MODEL
128 SVCERR << "AlignmentModel::isReady: no raw path" << endl;
129 #endif
130 return false;
131 }
132 auto pathSourceModel =
133 ModelById::getAs<SparseTimeValueModel>(m_pathSource);
134 if (pathSourceModel) {
135 return pathSourceModel->isReady(completion);
136 } else {
137 return true; // there is no meaningful answer here
138 }
139 }
140
141 const ZoomConstraint *
142 AlignmentModel::getZoomConstraint() const
143 {
144 return nullptr;
145 }
146
147 ModelId
148 AlignmentModel::getReferenceModel() const
149 {
pg_logging_config(int new_flags)150 return m_reference;
151 }
152
153 ModelId
154 AlignmentModel::getAlignedModel() const
155 {
pg_logging_set_level(enum pg_log_level new_level)156 return m_aligned;
157 }
158
159 sv_frame_t
160 AlignmentModel::toReference(sv_frame_t frame) const
161 {
pg_logging_set_pre_callback(void (* cb)(void))162 #ifdef DEBUG_ALIGNMENT_MODEL
163 cerr << "AlignmentModel::toReference(" << frame << ")" << endl;
164 #endif
165 if (!m_path) {
166 if (m_pathSource.isNone()) {
167 return frame;
pg_logging_set_locus_callback(void (* cb)(const char ** filename,uint64 * lineno))168 }
169 constructPath();
170 }
171 if (!m_path) {
172 return frame;
173 }
pg_log_generic(enum pg_log_level level,const char * pg_restrict fmt,...)174
175 return performAlignment(*m_path, frame);
176 }
177
178 sv_frame_t
179 AlignmentModel::fromReference(sv_frame_t frame) const
180 {
181 #ifdef DEBUG_ALIGNMENT_MODEL
182 cerr << "AlignmentModel::fromReference(" << frame << ")" << endl;
183 #endif
pg_log_generic_v(enum pg_log_level level,const char * pg_restrict fmt,va_list ap)184 if (!m_reversePath) {
185 if (m_pathSource.isNone()) {
186 return frame;
187 }
188 constructReversePath();
189 }
190 if (!m_reversePath) {
191 return frame;
192 }
193
194 return performAlignment(*m_reversePath, frame);
195 }
196
197 void
198 AlignmentModel::pathSourceChangedWithin(ModelId, sv_frame_t, sv_frame_t)
199 {
200 if (!m_pathComplete) return;
201 constructPath();
202 constructReversePath();
203 }
204
205 void
206 AlignmentModel::pathSourceCompletionChanged(ModelId)
207 {
208 auto pathSourceModel =
209 ModelById::getAs<SparseTimeValueModel>(m_pathSource);
210 if (!pathSourceModel) return;
211
212 m_pathBegun = true;
213
214 if (!m_pathComplete) {
215
216 int completion = 0;
217 pathSourceModel->isReady(&completion);
218
219 #ifdef DEBUG_ALIGNMENT_MODEL
220 SVCERR << "AlignmentModel::pathCompletionChanged: completion = "
221 << completion << endl;
222 #endif
223
224 m_pathComplete = (completion == 100);
225
226 if (m_pathComplete) {
227
228 constructPath();
229 constructReversePath();
230
231 #ifdef DEBUG_ALIGNMENT_MODEL
232 SVCERR << "AlignmentModel: path complete" << endl;
233 #endif
234 }
235 }
236
237 emit completionChanged(getId());
238 }
239
240 void
241 AlignmentModel::constructPath() const
242 {
243 auto alignedModel = ModelById::get(m_aligned);
244 if (!alignedModel) return;
245
246 auto pathSourceModel =
247 ModelById::getAs<SparseTimeValueModel>(m_pathSource);
248 if (!m_path) {
249 if (!pathSourceModel) {
250 cerr << "ERROR: AlignmentModel::constructPath: "
251 << "No raw path available (id is " << m_pathSource
252 << ")" << endl;
253 return;
254 }
255 m_path.reset(new Path
256 (pathSourceModel->getSampleRate(),
257 pathSourceModel->getResolution()));
258 } else {
259 if (!pathSourceModel) return;
260 }
261
262 m_path->clear();
263
264 EventVector points = pathSourceModel->getAllEvents();
265
266 for (const auto &p: points) {
267 sv_frame_t frame = p.getFrame();
268 double value = p.getValue();
269 sv_frame_t rframe = lrint(value * alignedModel->getSampleRate());
270 m_path->add(PathPoint(frame, rframe));
271 }
272
273 #ifdef DEBUG_ALIGNMENT_MODEL
274 cerr << "AlignmentModel::constructPath: " << m_path->getPointCount() << " points, at least " << (2 * m_path->getPointCount() * (3 * sizeof(void *) + sizeof(int) + sizeof(PathPoint))) << " bytes" << endl;
275 #endif
276 }
277
278 void
279 AlignmentModel::constructReversePath() const
280 {
281 if (!m_reversePath) {
282 if (!m_path) {
283 cerr << "ERROR: AlignmentModel::constructReversePath: "
284 << "No forward path available" << endl;
285 return;
286 }
287 m_reversePath.reset(new Path
288 (m_path->getSampleRate(),
289 m_path->getResolution()));
290 } else {
291 if (!m_path) return;
292 }
293
294 m_reversePath->clear();
295
296 Path::Points points = m_path->getPoints();
297
298 for (auto p: points) {
299 sv_frame_t frame = p.frame;
300 sv_frame_t rframe = p.mapframe;
301 m_reversePath->add(PathPoint(rframe, frame));
302 }
303
304 #ifdef DEBUG_ALIGNMENT_MODEL
305 cerr << "AlignmentModel::constructReversePath: " << m_reversePath->getPointCount() << " points, at least " << (2 * m_reversePath->getPointCount() * (3 * sizeof(void *) + sizeof(int) + sizeof(PathPoint))) << " bytes" << endl;
306 #endif
307 }
308
309 sv_frame_t
310 AlignmentModel::performAlignment(const Path &path, sv_frame_t frame) const
311 {
312 // The path consists of a series of points, each with frame equal
313 // to the frame on the source model and mapframe equal to the
314 // frame on the target model. Both should be monotonically
315 // increasing.
316
317 const Path::Points &points = path.getPoints();
318
319 if (points.empty()) {
320 #ifdef DEBUG_ALIGNMENT_MODEL
321 cerr << "AlignmentModel::align: No points" << endl;
322 #endif
323 return frame;
324 }
325
326 #ifdef DEBUG_ALIGNMENT_MODEL
327 cerr << "AlignmentModel::align: frame " << frame << " requested" << endl;
328 #endif
329
330 PathPoint point(frame);
331 Path::Points::const_iterator i = points.lower_bound(point);
332 if (i == points.end()) {
333 #ifdef DEBUG_ALIGNMENT_MODEL
334 cerr << "Note: i == points.end()" << endl;
335 #endif
336 --i;
337 }
338 while (i != points.begin() && i->frame > frame) {
339 --i;
340 }
341
342 sv_frame_t foundFrame = i->frame;
343 sv_frame_t foundMapFrame = i->mapframe;
344
345 sv_frame_t followingFrame = foundFrame;
346 sv_frame_t followingMapFrame = foundMapFrame;
347
348 if (++i != points.end()) {
349 #ifdef DEBUG_ALIGNMENT_MODEL
350 cerr << "another point available" << endl;
351 #endif
352 followingFrame = i->frame;
353 followingMapFrame = i->mapframe;
354 } else {
355 #ifdef DEBUG_ALIGNMENT_MODEL
356 cerr << "no other point available" << endl;
357 #endif
358 }
359
360 #ifdef DEBUG_ALIGNMENT_MODEL
361 cerr << "foundFrame = " << foundFrame << ", foundMapFrame = " << foundMapFrame
362 << ", followingFrame = " << followingFrame << ", followingMapFrame = "
363 << followingMapFrame << endl;
364 #endif
365
366 if (foundMapFrame < 0) {
367 return 0;
368 }
369
370 sv_frame_t resultFrame = foundMapFrame;
371
372 if (followingFrame != foundFrame && frame > foundFrame) {
373 double interp =
374 double(frame - foundFrame) /
375 double(followingFrame - foundFrame);
376 resultFrame += lrint(double(followingMapFrame - foundMapFrame) * interp);
377 }
378
379 #ifdef DEBUG_ALIGNMENT_MODEL
380 cerr << "AlignmentModel::align: resultFrame = " << resultFrame << endl;
381 #endif
382
383 return resultFrame;
384 }
385
386 void
387 AlignmentModel::setPathFrom(ModelId pathSource)
388 {
389 m_pathSource = pathSource;
390
391 auto pathSourceModel =
392 ModelById::getAs<SparseTimeValueModel>(m_pathSource);
393
394 if (pathSourceModel) {
395
396 connect(pathSourceModel.get(),
397 SIGNAL(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t)),
398 this, SLOT(pathSourceChangedWithin(ModelId, sv_frame_t, sv_frame_t)));
399
400 connect(pathSourceModel.get(), SIGNAL(completionChanged(ModelId)),
401 this, SLOT(pathSourceCompletionChanged(ModelId)));
402
403 constructPath();
404 constructReversePath();
405
406 if (pathSourceModel->isReady()) {
407 pathSourceCompletionChanged(m_pathSource);
408 }
409 }
410 }
411
412 void
413 AlignmentModel::setPath(const Path &path)
414 {
415 m_path.reset(new Path(path));
416 m_pathComplete = true;
417 constructReversePath();
418 }
419
420 void
421 AlignmentModel::toXml(QTextStream &stream,
422 QString indent,
423 QString extraAttributes) const
424 {
425 if (!m_path) {
426 SVDEBUG << "AlignmentModel::toXml: no path" << endl;
427 return;
428 }
429
430 m_path->toXml(stream, indent, "");
431
432 Model::toXml(stream, indent,
433 QString("type=\"alignment\" reference=\"%1\" aligned=\"%2\" path=\"%3\" %4")
434 .arg(ModelById::getExportId(m_reference))
435 .arg(ModelById::getExportId(m_aligned))
436 .arg(m_path->getExportId())
437 .arg(extraAttributes));
438 }
439