Chris@867
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@867
|
2
|
Chris@867
|
3 /*
|
Chris@867
|
4 Sonic Visualiser
|
Chris@867
|
5 An audio file viewer and annotation editor.
|
Chris@867
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@867
|
7 This file copyright 2006-2014 Chris Cannam and QMUL.
|
Chris@867
|
8
|
Chris@867
|
9 This program is free software; you can redistribute it and/or
|
Chris@867
|
10 modify it under the terms of the GNU General Public License as
|
Chris@867
|
11 published by the Free Software Foundation; either version 2 of the
|
Chris@867
|
12 License, or (at your option) any later version. See the file
|
Chris@867
|
13 COPYING included with this distribution for more information.
|
Chris@867
|
14 */
|
Chris@867
|
15
|
Chris@867
|
16 #include "AlignmentView.h"
|
Chris@867
|
17
|
Chris@867
|
18 #include <QPainter>
|
Chris@867
|
19
|
Chris@868
|
20 #include "data/model/SparseOneDimensionalModel.h"
|
Chris@868
|
21
|
Chris@868
|
22 #include "layer/TimeInstantLayer.h"
|
Chris@868
|
23
|
Chris@1493
|
24 //#define DEBUG_ALIGNMENT_VIEW 1
|
Chris@1493
|
25
|
Chris@867
|
26 using std::vector;
|
Chris@1493
|
27 using std::set;
|
Chris@867
|
28
|
Chris@867
|
29 AlignmentView::AlignmentView(QWidget *w) :
|
Chris@867
|
30 View(w, false),
|
Chris@1408
|
31 m_above(nullptr),
|
Chris@1615
|
32 m_below(nullptr),
|
Chris@1615
|
33 m_reference(nullptr),
|
Chris@1615
|
34 m_leftmostAbove(-1),
|
Chris@1615
|
35 m_rightmostAbove(-1)
|
Chris@867
|
36 {
|
Chris@867
|
37 setObjectName(tr("AlignmentView"));
|
Chris@867
|
38 }
|
Chris@867
|
39
|
Chris@867
|
40 void
|
Chris@1493
|
41 AlignmentView::keyFramesChanged()
|
Chris@1493
|
42 {
|
Chris@1493
|
43 #ifdef DEBUG_ALIGNMENT_VIEW
|
Chris@1493
|
44 SVCERR << "AlignmentView " << getId() << "::keyFramesChanged" << endl;
|
Chris@1493
|
45 #endif
|
Chris@1493
|
46
|
Chris@1615
|
47 // This is just a notification that we need to rebuild - so all we
|
Chris@1615
|
48 // do here is clear, and rebuild on demand later
|
Chris@1615
|
49 QMutexLocker locker(&m_mapsMutex);
|
Chris@1615
|
50 m_fromAboveMap.clear();
|
Chris@1615
|
51 m_fromReferenceMap.clear();
|
Chris@1493
|
52 }
|
Chris@1493
|
53
|
Chris@1493
|
54 void
|
Chris@976
|
55 AlignmentView::globalCentreFrameChanged(sv_frame_t f)
|
Chris@867
|
56 {
|
Chris@867
|
57 View::globalCentreFrameChanged(f);
|
Chris@867
|
58 update();
|
Chris@867
|
59 }
|
Chris@867
|
60
|
Chris@867
|
61 void
|
Chris@976
|
62 AlignmentView::viewCentreFrameChanged(View *v, sv_frame_t f)
|
Chris@867
|
63 {
|
Chris@867
|
64 View::viewCentreFrameChanged(v, f);
|
Chris@867
|
65 if (v == m_above) {
|
Chris@1266
|
66 m_centreFrame = f;
|
Chris@1266
|
67 update();
|
Chris@867
|
68 } else if (v == m_below) {
|
Chris@1266
|
69 update();
|
Chris@867
|
70 }
|
Chris@867
|
71 }
|
Chris@867
|
72
|
Chris@867
|
73 void
|
Chris@976
|
74 AlignmentView::viewManagerPlaybackFrameChanged(sv_frame_t)
|
Chris@867
|
75 {
|
Chris@867
|
76 update();
|
Chris@867
|
77 }
|
Chris@867
|
78
|
Chris@867
|
79 void
|
Chris@1183
|
80 AlignmentView::viewAboveZoomLevelChanged(ZoomLevel level, bool)
|
Chris@867
|
81 {
|
Chris@867
|
82 m_zoomLevel = level;
|
Chris@867
|
83 update();
|
Chris@867
|
84 }
|
Chris@867
|
85
|
Chris@867
|
86 void
|
Chris@1183
|
87 AlignmentView::viewBelowZoomLevelChanged(ZoomLevel, bool)
|
Chris@867
|
88 {
|
Chris@867
|
89 update();
|
Chris@867
|
90 }
|
Chris@867
|
91
|
Chris@867
|
92 void
|
Chris@1615
|
93 AlignmentView::setAboveView(View *v)
|
Chris@867
|
94 {
|
Chris@867
|
95 if (m_above) {
|
Chris@1408
|
96 disconnect(m_above, nullptr, this, nullptr);
|
Chris@867
|
97 }
|
Chris@867
|
98
|
Chris@867
|
99 m_above = v;
|
Chris@867
|
100
|
Chris@867
|
101 if (m_above) {
|
Chris@1266
|
102 connect(m_above,
|
Chris@1183
|
103 SIGNAL(zoomLevelChanged(ZoomLevel, bool)),
|
Chris@1266
|
104 this,
|
Chris@1183
|
105 SLOT(viewAboveZoomLevelChanged(ZoomLevel, bool)));
|
Chris@1493
|
106 connect(m_above,
|
Chris@1493
|
107 SIGNAL(propertyContainerAdded(PropertyContainer *)),
|
Chris@1493
|
108 this,
|
Chris@1493
|
109 SLOT(keyFramesChanged()));
|
Chris@1493
|
110 connect(m_above,
|
Chris@1493
|
111 SIGNAL(layerModelChanged()),
|
Chris@1493
|
112 this,
|
Chris@1493
|
113 SLOT(keyFramesChanged()));
|
Chris@867
|
114 }
|
Chris@1493
|
115
|
Chris@1493
|
116 keyFramesChanged();
|
Chris@867
|
117 }
|
Chris@867
|
118
|
Chris@867
|
119 void
|
Chris@1615
|
120 AlignmentView::setBelowView(View *v)
|
Chris@867
|
121 {
|
Chris@867
|
122 if (m_below) {
|
Chris@1408
|
123 disconnect(m_below, nullptr, this, nullptr);
|
Chris@867
|
124 }
|
Chris@867
|
125
|
Chris@867
|
126 m_below = v;
|
Chris@867
|
127
|
Chris@867
|
128 if (m_below) {
|
Chris@1266
|
129 connect(m_below,
|
Chris@1183
|
130 SIGNAL(zoomLevelChanged(ZoomLevel, bool)),
|
Chris@1266
|
131 this,
|
Chris@1183
|
132 SLOT(viewBelowZoomLevelChanged(ZoomLevel, bool)));
|
Chris@1493
|
133 connect(m_below,
|
Chris@1493
|
134 SIGNAL(propertyContainerAdded(PropertyContainer *)),
|
Chris@1493
|
135 this,
|
Chris@1493
|
136 SLOT(keyFramesChanged()));
|
Chris@1493
|
137 connect(m_below,
|
Chris@1493
|
138 SIGNAL(layerModelChanged()),
|
Chris@1493
|
139 this,
|
Chris@1493
|
140 SLOT(keyFramesChanged()));
|
Chris@867
|
141 }
|
Chris@1493
|
142
|
Chris@1493
|
143 keyFramesChanged();
|
Chris@867
|
144 }
|
Chris@867
|
145
|
Chris@867
|
146 void
|
Chris@1615
|
147 AlignmentView::setReferenceView(View *view)
|
Chris@1615
|
148 {
|
Chris@1615
|
149 m_reference = view;
|
Chris@1615
|
150 }
|
Chris@1615
|
151
|
Chris@1615
|
152 void
|
Chris@867
|
153 AlignmentView::paintEvent(QPaintEvent *)
|
Chris@867
|
154 {
|
Chris@1408
|
155 if (m_above == nullptr || m_below == nullptr || !m_manager) return;
|
Chris@1493
|
156
|
Chris@1493
|
157 #ifdef DEBUG_ALIGNMENT_VIEW
|
Chris@1493
|
158 SVCERR << "AlignmentView " << getId() << "::paintEvent" << endl;
|
Chris@1493
|
159 #endif
|
Chris@1493
|
160
|
Chris@867
|
161 bool darkPalette = false;
|
Chris@867
|
162 if (m_manager) darkPalette = m_manager->getGlobalDarkBackground();
|
Chris@867
|
163
|
Chris@881
|
164 QColor fg, bg;
|
Chris@881
|
165 if (darkPalette) {
|
Chris@881
|
166 fg = Qt::gray;
|
Chris@881
|
167 bg = Qt::black;
|
Chris@881
|
168 } else {
|
Chris@881
|
169 fg = Qt::black;
|
Chris@881
|
170 bg = Qt::gray;
|
Chris@881
|
171 }
|
Chris@867
|
172
|
Chris@867
|
173 QPainter paint(this);
|
Chris@867
|
174 paint.setPen(QPen(fg, 2));
|
Chris@867
|
175 paint.setBrush(Qt::NoBrush);
|
Chris@867
|
176 paint.setRenderHint(QPainter::Antialiasing, true);
|
Chris@867
|
177
|
Chris@867
|
178 paint.fillRect(rect(), bg);
|
Chris@867
|
179
|
Chris@1615
|
180 QMutexLocker locker(&m_mapsMutex);
|
Chris@867
|
181
|
Chris@1615
|
182 if (m_fromAboveMap.empty()) {
|
Chris@1493
|
183 reconnectModels();
|
Chris@1615
|
184 buildMaps();
|
Chris@1493
|
185 }
|
Chris@1493
|
186
|
Chris@1493
|
187 #ifdef DEBUG_ALIGNMENT_VIEW
|
Chris@1493
|
188 SVCERR << "AlignmentView " << getId() << "::paintEvent: painting "
|
Chris@1615
|
189 << m_fromAboveMap.size() << " mappings" << endl;
|
Chris@1493
|
190 #endif
|
Chris@1493
|
191
|
Chris@1615
|
192 int w = width();
|
Chris@1615
|
193 int h = height();
|
Chris@1493
|
194
|
Chris@1619
|
195 if (m_leftmostAbove >= 0) {
|
Chris@1619
|
196
|
Chris@1619
|
197 #ifdef DEBUG_ALIGNMENT_VIEW
|
Chris@1619
|
198 SVCERR << "AlignmentView: m_leftmostAbove = " << m_leftmostAbove
|
Chris@1619
|
199 << ", we have a relationship with the pane above us: showing "
|
Chris@1619
|
200 << "mappings in relation to that" << endl;
|
Chris@1619
|
201 #endif
|
Chris@1619
|
202
|
Chris@1615
|
203 for (const auto &km: m_fromAboveMap) {
|
Chris@1493
|
204
|
Chris@1615
|
205 sv_frame_t af = km.first;
|
Chris@1615
|
206 sv_frame_t bf = km.second;
|
Chris@1615
|
207
|
Chris@1620
|
208 if (af < m_leftmostAbove) {
|
Chris@1620
|
209 #ifdef DEBUG_ALIGNMENT_VIEW
|
Chris@1620
|
210 SVCERR << "AlignmentView: af " << af << " < m_leftmostAbove " << m_leftmostAbove << endl;
|
Chris@1620
|
211 #endif
|
Chris@1620
|
212 continue;
|
Chris@1620
|
213 }
|
Chris@1620
|
214 if (af > m_rightmostAbove) {
|
Chris@1620
|
215 #ifdef DEBUG_ALIGNMENT_VIEW
|
Chris@1620
|
216 SVCERR << "AlignmentView: af " << af << " > m_rightmostAbove " << m_rightmostAbove << endl;
|
Chris@1620
|
217 #endif
|
Chris@1615
|
218 continue;
|
Chris@1615
|
219 }
|
Chris@1493
|
220
|
Chris@1615
|
221 int ax = m_above->getXForFrame(af);
|
Chris@1615
|
222 int bx = m_below->getXForFrame(bf);
|
Chris@1615
|
223
|
Chris@1615
|
224 if (ax >= 0 || ax < w || bx >= 0 || bx < w) {
|
Chris@1615
|
225 paint.drawLine(ax, 0, bx, h);
|
Chris@1615
|
226 }
|
Chris@1615
|
227 }
|
Chris@1615
|
228 } else if (m_reference != nullptr) {
|
Chris@1615
|
229 // the below has nothing in common with the above: show things
|
Chris@1615
|
230 // in common with the reference instead
|
Chris@1619
|
231
|
Chris@1619
|
232 #ifdef DEBUG_ALIGNMENT_VIEW
|
Chris@1619
|
233 SVCERR << "AlignmentView: m_leftmostAbove = " << m_leftmostAbove
|
Chris@1619
|
234 << ", we have no relationship with the pane above us: showing "
|
Chris@1619
|
235 << "mappings in relation to the reference instead" << endl;
|
Chris@1619
|
236 #endif
|
Chris@1619
|
237
|
Chris@1615
|
238 for (const auto &km: m_fromReferenceMap) {
|
Chris@1615
|
239
|
Chris@1615
|
240 sv_frame_t af = km.first;
|
Chris@1615
|
241 sv_frame_t bf = km.second;
|
Chris@1615
|
242
|
Chris@1615
|
243 int ax = m_reference->getXForFrame(af);
|
Chris@1615
|
244 int bx = m_below->getXForFrame(bf);
|
Chris@1615
|
245
|
Chris@1615
|
246 if (ax >= 0 || ax < w || bx >= 0 || bx < w) {
|
Chris@1615
|
247 paint.drawLine(ax, 0, bx, h);
|
Chris@1615
|
248 }
|
Chris@1493
|
249 }
|
Chris@867
|
250 }
|
Chris@867
|
251
|
Chris@867
|
252 paint.end();
|
Chris@1493
|
253 }
|
Chris@867
|
254
|
Chris@1493
|
255 void
|
Chris@1493
|
256 AlignmentView::reconnectModels()
|
Chris@868
|
257 {
|
Chris@1493
|
258 vector<ModelId> toConnect {
|
Chris@1493
|
259 getSalientModel(m_above),
|
Chris@1493
|
260 getSalientModel(m_below)
|
Chris@1493
|
261 };
|
Chris@868
|
262
|
Chris@1493
|
263 for (auto modelId: toConnect) {
|
Chris@1493
|
264 if (auto model = ModelById::get(modelId)) {
|
Chris@1493
|
265 auto referenceId = model->getAlignmentReference();
|
Chris@1493
|
266 if (!referenceId.isNone()) {
|
Chris@1493
|
267 toConnect.push_back(referenceId);
|
Chris@1475
|
268 }
|
Chris@1266
|
269 }
|
Chris@868
|
270 }
|
Chris@868
|
271
|
Chris@1493
|
272 for (auto modelId: toConnect) {
|
Chris@1493
|
273 if (auto model = ModelById::get(modelId)) {
|
Chris@1493
|
274 auto ptr = model.get();
|
Chris@1493
|
275 disconnect(ptr, 0, this, 0);
|
Chris@1493
|
276 connect(ptr, SIGNAL(modelChanged(ModelId)),
|
Chris@1493
|
277 this, SLOT(keyFramesChanged()));
|
Chris@1493
|
278 connect(ptr, SIGNAL(completionChanged(ModelId)),
|
Chris@1493
|
279 this, SLOT(keyFramesChanged()));
|
Chris@1493
|
280 connect(ptr, SIGNAL(alignmentCompletionChanged(ModelId)),
|
Chris@1493
|
281 this, SLOT(keyFramesChanged()));
|
Chris@1493
|
282 }
|
Chris@1493
|
283 }
|
Chris@1493
|
284 }
|
Chris@1493
|
285
|
Chris@1493
|
286 void
|
Chris@1615
|
287 AlignmentView::buildMaps()
|
Chris@1493
|
288 {
|
Chris@1493
|
289 #ifdef DEBUG_ALIGNMENT_VIEW
|
Chris@1615
|
290 SVCERR << "AlignmentView " << getId() << "::buildMaps" << endl;
|
Chris@1493
|
291 #endif
|
Chris@1493
|
292
|
Chris@1493
|
293 sv_frame_t resolution = 1;
|
Chris@1493
|
294
|
Chris@1493
|
295 set<sv_frame_t> keyFramesBelow;
|
Chris@1493
|
296 for (auto f: getKeyFrames(m_below, resolution)) {
|
Chris@1493
|
297 keyFramesBelow.insert(f);
|
Chris@1493
|
298 }
|
Chris@1493
|
299
|
Chris@1615
|
300 foreach(sv_frame_t f, keyFramesBelow) {
|
Chris@1615
|
301 sv_frame_t rf = m_below->alignToReference(f);
|
Chris@1615
|
302 m_fromReferenceMap.insert({ rf, f });
|
Chris@1615
|
303 }
|
Chris@1615
|
304
|
Chris@1493
|
305 vector<sv_frame_t> keyFrames = getKeyFrames(m_above, resolution);
|
Chris@1493
|
306
|
Chris@1615
|
307 // These are the most extreme leftward and rightward frames in
|
Chris@1615
|
308 // "above" that have distinct corresponding frames in
|
Chris@1615
|
309 // "below". Anything left of m_leftmostAbove or right of
|
Chris@1615
|
310 // m_rightmostAbove maps effectively off one end or the other of
|
Chris@1615
|
311 // the below view. (They don't actually map off the ends, they
|
Chris@1615
|
312 // just all map to the same first/last destination frame. But we
|
Chris@1615
|
313 // don't want to display their mappings, as they're just noise.)
|
Chris@1615
|
314 m_leftmostAbove = -1;
|
Chris@1615
|
315 m_rightmostAbove = -1;
|
Chris@1615
|
316
|
Chris@1620
|
317 sv_frame_t prevAf = -1;
|
Chris@1615
|
318 sv_frame_t prevBf = -1;
|
Chris@1615
|
319
|
Chris@1620
|
320 foreach (sv_frame_t af, keyFrames) {
|
Chris@1493
|
321
|
Chris@1620
|
322 sv_frame_t rf = m_above->alignToReference(af);
|
Chris@1493
|
323 sv_frame_t bf = m_below->alignFromReference(rf);
|
Chris@1493
|
324
|
Chris@1615
|
325 if (prevBf > 0 && bf > prevBf) {
|
Chris@1620
|
326 if (m_leftmostAbove < 0) {
|
Chris@1620
|
327 m_leftmostAbove = prevAf;
|
Chris@1615
|
328 }
|
Chris@1620
|
329 m_rightmostAbove = af;
|
Chris@1615
|
330 }
|
Chris@1620
|
331 prevAf = af;
|
Chris@1615
|
332 prevBf = bf;
|
Chris@1615
|
333
|
Chris@1493
|
334 bool mappedSomething = false;
|
Chris@1493
|
335
|
Chris@1493
|
336 if (resolution > 1) {
|
Chris@1493
|
337 if (keyFramesBelow.find(bf) == keyFramesBelow.end()) {
|
Chris@1493
|
338
|
Chris@1620
|
339 sv_frame_t af1 = af + resolution;
|
Chris@1620
|
340 sv_frame_t rf1 = m_above->alignToReference(af1);
|
Chris@1493
|
341 sv_frame_t bf1 = m_below->alignFromReference(rf1);
|
Chris@1493
|
342
|
Chris@1493
|
343 for (sv_frame_t probe = bf + 1; probe <= bf1; ++probe) {
|
Chris@1493
|
344 if (keyFramesBelow.find(probe) != keyFramesBelow.end()) {
|
Chris@1620
|
345 m_fromAboveMap.insert({ af, probe });
|
Chris@1493
|
346 mappedSomething = true;
|
Chris@1493
|
347 }
|
Chris@1493
|
348 }
|
Chris@1493
|
349 }
|
Chris@1493
|
350 }
|
Chris@1493
|
351
|
Chris@1493
|
352 if (!mappedSomething) {
|
Chris@1620
|
353 m_fromAboveMap.insert({ af, bf });
|
Chris@1493
|
354 }
|
Chris@1493
|
355 }
|
Chris@1493
|
356
|
Chris@1493
|
357 #ifdef DEBUG_ALIGNMENT_VIEW
|
Chris@1615
|
358 SVCERR << "AlignmentView " << getId() << "::buildMaps: have "
|
Chris@1615
|
359 << m_fromAboveMap.size() << " mappings" << endl;
|
Chris@1493
|
360 #endif
|
Chris@1493
|
361 }
|
Chris@1493
|
362
|
Chris@1493
|
363 vector<sv_frame_t>
|
Chris@1493
|
364 AlignmentView::getKeyFrames(View *view, sv_frame_t &resolution)
|
Chris@1493
|
365 {
|
Chris@1493
|
366 resolution = 1;
|
Chris@1493
|
367
|
Chris@1493
|
368 if (!view) {
|
Chris@1493
|
369 return getDefaultKeyFrames();
|
Chris@1493
|
370 }
|
Chris@1493
|
371
|
Chris@1493
|
372 ModelId m = getSalientModel(view);
|
Chris@1475
|
373 auto model = ModelById::getAs<SparseOneDimensionalModel>(m);
|
Chris@1475
|
374 if (!model) {
|
Chris@1266
|
375 return getDefaultKeyFrames();
|
Chris@868
|
376 }
|
Chris@868
|
377
|
Chris@1493
|
378 resolution = model->getResolution();
|
Chris@1493
|
379
|
Chris@976
|
380 vector<sv_frame_t> keyFrames;
|
Chris@868
|
381
|
Chris@1475
|
382 EventVector pp = model->getAllEvents();
|
Chris@1433
|
383 for (EventVector::const_iterator pi = pp.begin(); pi != pp.end(); ++pi) {
|
Chris@1433
|
384 keyFrames.push_back(pi->getFrame());
|
Chris@868
|
385 }
|
Chris@868
|
386
|
Chris@868
|
387 return keyFrames;
|
Chris@868
|
388 }
|
Chris@868
|
389
|
Chris@976
|
390 vector<sv_frame_t>
|
Chris@868
|
391 AlignmentView::getDefaultKeyFrames()
|
Chris@868
|
392 {
|
Chris@976
|
393 vector<sv_frame_t> keyFrames;
|
Chris@1509
|
394 return keyFrames;
|
Chris@868
|
395
|
Chris@1509
|
396 #ifdef NOT_REALLY
|
Chris@868
|
397 if (!m_above || !m_manager) return keyFrames;
|
Chris@868
|
398
|
Chris@976
|
399 sv_samplerate_t rate = m_manager->getMainModelSampleRate();
|
Chris@868
|
400 if (rate == 0) return keyFrames;
|
Chris@868
|
401
|
Chris@976
|
402 for (sv_frame_t f = m_above->getModelsStartFrame();
|
Chris@1266
|
403 f <= m_above->getModelsEndFrame();
|
Chris@1266
|
404 f += sv_frame_t(rate * 5 + 0.5)) {
|
Chris@1266
|
405 keyFrames.push_back(f);
|
Chris@868
|
406 }
|
Chris@868
|
407
|
Chris@868
|
408 return keyFrames;
|
Chris@1509
|
409 #endif
|
Chris@868
|
410 }
|
Chris@868
|
411
|
Chris@1493
|
412 ModelId
|
Chris@1493
|
413 AlignmentView::getSalientModel(View *view)
|
Chris@1493
|
414 {
|
Chris@1493
|
415 ModelId m;
|
Chris@1493
|
416
|
Chris@1493
|
417 // get the topmost such
|
Chris@1493
|
418 for (int i = 0; i < view->getLayerCount(); ++i) {
|
Chris@1493
|
419 if (qobject_cast<TimeInstantLayer *>(view->getLayer(i))) {
|
Chris@1493
|
420 ModelId mm = view->getLayer(i)->getModel();
|
Chris@1493
|
421 if (ModelById::isa<SparseOneDimensionalModel>(mm)) {
|
Chris@1493
|
422 m = mm;
|
Chris@1493
|
423 }
|
Chris@1493
|
424 }
|
Chris@1493
|
425 }
|
Chris@1493
|
426
|
Chris@1493
|
427 return m;
|
Chris@1493
|
428 }
|
Chris@1493
|
429
|
Chris@1493
|
430
|