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