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@1615
|
195 if (m_leftmostAbove > 0) {
|
Chris@1615
|
196
|
Chris@1615
|
197 for (const auto &km: m_fromAboveMap) {
|
Chris@1493
|
198
|
Chris@1615
|
199 sv_frame_t af = km.first;
|
Chris@1615
|
200 sv_frame_t bf = km.second;
|
Chris@1615
|
201
|
Chris@1615
|
202 if (af < m_leftmostAbove || af > m_rightmostAbove) {
|
Chris@1615
|
203 continue;
|
Chris@1615
|
204 }
|
Chris@1493
|
205
|
Chris@1615
|
206 int ax = m_above->getXForFrame(af);
|
Chris@1615
|
207 int bx = m_below->getXForFrame(bf);
|
Chris@1615
|
208
|
Chris@1615
|
209 if (ax >= 0 || ax < w || bx >= 0 || bx < w) {
|
Chris@1615
|
210 paint.drawLine(ax, 0, bx, h);
|
Chris@1615
|
211 }
|
Chris@1615
|
212 }
|
Chris@1615
|
213 } else if (m_reference != nullptr) {
|
Chris@1615
|
214 // the below has nothing in common with the above: show things
|
Chris@1615
|
215 // in common with the reference instead
|
Chris@1615
|
216
|
Chris@1615
|
217 for (const auto &km: m_fromReferenceMap) {
|
Chris@1615
|
218
|
Chris@1615
|
219 sv_frame_t af = km.first;
|
Chris@1615
|
220 sv_frame_t bf = km.second;
|
Chris@1615
|
221
|
Chris@1615
|
222 int ax = m_reference->getXForFrame(af);
|
Chris@1615
|
223 int bx = m_below->getXForFrame(bf);
|
Chris@1615
|
224
|
Chris@1615
|
225 if (ax >= 0 || ax < w || bx >= 0 || bx < w) {
|
Chris@1615
|
226 paint.drawLine(ax, 0, bx, h);
|
Chris@1615
|
227 }
|
Chris@1493
|
228 }
|
Chris@867
|
229 }
|
Chris@867
|
230
|
Chris@867
|
231 paint.end();
|
Chris@1493
|
232 }
|
Chris@867
|
233
|
Chris@1493
|
234 void
|
Chris@1493
|
235 AlignmentView::reconnectModels()
|
Chris@868
|
236 {
|
Chris@1493
|
237 vector<ModelId> toConnect {
|
Chris@1493
|
238 getSalientModel(m_above),
|
Chris@1493
|
239 getSalientModel(m_below)
|
Chris@1493
|
240 };
|
Chris@868
|
241
|
Chris@1493
|
242 for (auto modelId: toConnect) {
|
Chris@1493
|
243 if (auto model = ModelById::get(modelId)) {
|
Chris@1493
|
244 auto referenceId = model->getAlignmentReference();
|
Chris@1493
|
245 if (!referenceId.isNone()) {
|
Chris@1493
|
246 toConnect.push_back(referenceId);
|
Chris@1475
|
247 }
|
Chris@1266
|
248 }
|
Chris@868
|
249 }
|
Chris@868
|
250
|
Chris@1493
|
251 for (auto modelId: toConnect) {
|
Chris@1493
|
252 if (auto model = ModelById::get(modelId)) {
|
Chris@1493
|
253 auto ptr = model.get();
|
Chris@1493
|
254 disconnect(ptr, 0, this, 0);
|
Chris@1493
|
255 connect(ptr, SIGNAL(modelChanged(ModelId)),
|
Chris@1493
|
256 this, SLOT(keyFramesChanged()));
|
Chris@1493
|
257 connect(ptr, SIGNAL(completionChanged(ModelId)),
|
Chris@1493
|
258 this, SLOT(keyFramesChanged()));
|
Chris@1493
|
259 connect(ptr, SIGNAL(alignmentCompletionChanged(ModelId)),
|
Chris@1493
|
260 this, SLOT(keyFramesChanged()));
|
Chris@1493
|
261 }
|
Chris@1493
|
262 }
|
Chris@1493
|
263 }
|
Chris@1493
|
264
|
Chris@1493
|
265 void
|
Chris@1615
|
266 AlignmentView::buildMaps()
|
Chris@1493
|
267 {
|
Chris@1493
|
268 #ifdef DEBUG_ALIGNMENT_VIEW
|
Chris@1615
|
269 SVCERR << "AlignmentView " << getId() << "::buildMaps" << endl;
|
Chris@1493
|
270 #endif
|
Chris@1493
|
271
|
Chris@1493
|
272 sv_frame_t resolution = 1;
|
Chris@1493
|
273
|
Chris@1493
|
274 set<sv_frame_t> keyFramesBelow;
|
Chris@1493
|
275 for (auto f: getKeyFrames(m_below, resolution)) {
|
Chris@1493
|
276 keyFramesBelow.insert(f);
|
Chris@1493
|
277 }
|
Chris@1493
|
278
|
Chris@1615
|
279 foreach(sv_frame_t f, keyFramesBelow) {
|
Chris@1615
|
280 sv_frame_t rf = m_below->alignToReference(f);
|
Chris@1615
|
281 m_fromReferenceMap.insert({ rf, f });
|
Chris@1615
|
282 }
|
Chris@1615
|
283
|
Chris@1493
|
284 vector<sv_frame_t> keyFrames = getKeyFrames(m_above, resolution);
|
Chris@1493
|
285
|
Chris@1615
|
286 // These are the most extreme leftward and rightward frames in
|
Chris@1615
|
287 // "above" that have distinct corresponding frames in
|
Chris@1615
|
288 // "below". Anything left of m_leftmostAbove or right of
|
Chris@1615
|
289 // m_rightmostAbove maps effectively off one end or the other of
|
Chris@1615
|
290 // the below view. (They don't actually map off the ends, they
|
Chris@1615
|
291 // just all map to the same first/last destination frame. But we
|
Chris@1615
|
292 // don't want to display their mappings, as they're just noise.)
|
Chris@1615
|
293 m_leftmostAbove = -1;
|
Chris@1615
|
294 m_rightmostAbove = -1;
|
Chris@1615
|
295
|
Chris@1615
|
296 sv_frame_t prevRf = -1;
|
Chris@1615
|
297 sv_frame_t prevBf = -1;
|
Chris@1615
|
298
|
Chris@1493
|
299 foreach (sv_frame_t f, keyFrames) {
|
Chris@1493
|
300
|
Chris@1493
|
301 sv_frame_t rf = m_above->alignToReference(f);
|
Chris@1493
|
302 sv_frame_t bf = m_below->alignFromReference(rf);
|
Chris@1493
|
303
|
Chris@1615
|
304 if (prevBf > 0 && bf > prevBf) {
|
Chris@1615
|
305 if (m_leftmostAbove < 0 && prevBf > 0 && bf > prevBf) {
|
Chris@1615
|
306 m_leftmostAbove = prevRf;
|
Chris@1615
|
307 }
|
Chris@1615
|
308 m_rightmostAbove = rf;
|
Chris@1615
|
309 }
|
Chris@1615
|
310 prevRf = rf;
|
Chris@1615
|
311 prevBf = bf;
|
Chris@1615
|
312
|
Chris@1493
|
313 bool mappedSomething = false;
|
Chris@1493
|
314
|
Chris@1493
|
315 if (resolution > 1) {
|
Chris@1493
|
316 if (keyFramesBelow.find(bf) == keyFramesBelow.end()) {
|
Chris@1493
|
317
|
Chris@1493
|
318 sv_frame_t f1 = f + resolution;
|
Chris@1493
|
319 sv_frame_t rf1 = m_above->alignToReference(f1);
|
Chris@1493
|
320 sv_frame_t bf1 = m_below->alignFromReference(rf1);
|
Chris@1493
|
321
|
Chris@1493
|
322 for (sv_frame_t probe = bf + 1; probe <= bf1; ++probe) {
|
Chris@1493
|
323 if (keyFramesBelow.find(probe) != keyFramesBelow.end()) {
|
Chris@1615
|
324 m_fromAboveMap.insert({ f, probe });
|
Chris@1493
|
325 mappedSomething = true;
|
Chris@1493
|
326 }
|
Chris@1493
|
327 }
|
Chris@1493
|
328 }
|
Chris@1493
|
329 }
|
Chris@1493
|
330
|
Chris@1493
|
331 if (!mappedSomething) {
|
Chris@1615
|
332 m_fromAboveMap.insert({ f, bf });
|
Chris@1493
|
333 }
|
Chris@1493
|
334 }
|
Chris@1493
|
335
|
Chris@1493
|
336 #ifdef DEBUG_ALIGNMENT_VIEW
|
Chris@1615
|
337 SVCERR << "AlignmentView " << getId() << "::buildMaps: have "
|
Chris@1615
|
338 << m_fromAboveMap.size() << " mappings" << endl;
|
Chris@1493
|
339 #endif
|
Chris@1493
|
340 }
|
Chris@1493
|
341
|
Chris@1493
|
342 vector<sv_frame_t>
|
Chris@1493
|
343 AlignmentView::getKeyFrames(View *view, sv_frame_t &resolution)
|
Chris@1493
|
344 {
|
Chris@1493
|
345 resolution = 1;
|
Chris@1493
|
346
|
Chris@1493
|
347 if (!view) {
|
Chris@1493
|
348 return getDefaultKeyFrames();
|
Chris@1493
|
349 }
|
Chris@1493
|
350
|
Chris@1493
|
351 ModelId m = getSalientModel(view);
|
Chris@1475
|
352 auto model = ModelById::getAs<SparseOneDimensionalModel>(m);
|
Chris@1475
|
353 if (!model) {
|
Chris@1266
|
354 return getDefaultKeyFrames();
|
Chris@868
|
355 }
|
Chris@868
|
356
|
Chris@1493
|
357 resolution = model->getResolution();
|
Chris@1493
|
358
|
Chris@976
|
359 vector<sv_frame_t> keyFrames;
|
Chris@868
|
360
|
Chris@1475
|
361 EventVector pp = model->getAllEvents();
|
Chris@1433
|
362 for (EventVector::const_iterator pi = pp.begin(); pi != pp.end(); ++pi) {
|
Chris@1433
|
363 keyFrames.push_back(pi->getFrame());
|
Chris@868
|
364 }
|
Chris@868
|
365
|
Chris@868
|
366 return keyFrames;
|
Chris@868
|
367 }
|
Chris@868
|
368
|
Chris@976
|
369 vector<sv_frame_t>
|
Chris@868
|
370 AlignmentView::getDefaultKeyFrames()
|
Chris@868
|
371 {
|
Chris@976
|
372 vector<sv_frame_t> keyFrames;
|
Chris@1509
|
373 return keyFrames;
|
Chris@868
|
374
|
Chris@1509
|
375 #ifdef NOT_REALLY
|
Chris@868
|
376 if (!m_above || !m_manager) return keyFrames;
|
Chris@868
|
377
|
Chris@976
|
378 sv_samplerate_t rate = m_manager->getMainModelSampleRate();
|
Chris@868
|
379 if (rate == 0) return keyFrames;
|
Chris@868
|
380
|
Chris@976
|
381 for (sv_frame_t f = m_above->getModelsStartFrame();
|
Chris@1266
|
382 f <= m_above->getModelsEndFrame();
|
Chris@1266
|
383 f += sv_frame_t(rate * 5 + 0.5)) {
|
Chris@1266
|
384 keyFrames.push_back(f);
|
Chris@868
|
385 }
|
Chris@868
|
386
|
Chris@868
|
387 return keyFrames;
|
Chris@1509
|
388 #endif
|
Chris@868
|
389 }
|
Chris@868
|
390
|
Chris@1493
|
391 ModelId
|
Chris@1493
|
392 AlignmentView::getSalientModel(View *view)
|
Chris@1493
|
393 {
|
Chris@1493
|
394 ModelId m;
|
Chris@1493
|
395
|
Chris@1493
|
396 // get the topmost such
|
Chris@1493
|
397 for (int i = 0; i < view->getLayerCount(); ++i) {
|
Chris@1493
|
398 if (qobject_cast<TimeInstantLayer *>(view->getLayer(i))) {
|
Chris@1493
|
399 ModelId mm = view->getLayer(i)->getModel();
|
Chris@1493
|
400 if (ModelById::isa<SparseOneDimensionalModel>(mm)) {
|
Chris@1493
|
401 m = mm;
|
Chris@1493
|
402 }
|
Chris@1493
|
403 }
|
Chris@1493
|
404 }
|
Chris@1493
|
405
|
Chris@1493
|
406 return m;
|
Chris@1493
|
407 }
|
Chris@1493
|
408
|
Chris@1493
|
409
|