Chris@923
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@923
|
2
|
Chris@923
|
3 /*
|
Chris@923
|
4 Sonic Visualiser
|
Chris@923
|
5 An audio file viewer and annotation editor.
|
Chris@923
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@923
|
7
|
Chris@923
|
8 This program is free software; you can redistribute it and/or
|
Chris@923
|
9 modify it under the terms of the GNU General Public License as
|
Chris@923
|
10 published by the Free Software Foundation; either version 2 of the
|
Chris@923
|
11 License, or (at your option) any later version. See the file
|
Chris@923
|
12 COPYING included with this distribution for more information.
|
Chris@923
|
13 */
|
Chris@923
|
14
|
Chris@923
|
15 #include "LevelPanWidget.h"
|
Chris@923
|
16
|
Chris@923
|
17 #include <QPainter>
|
Chris@923
|
18 #include <QMouseEvent>
|
Chris@923
|
19 #include <QWheelEvent>
|
Chris@923
|
20
|
Chris@923
|
21 #include "layer/ColourMapper.h"
|
Chris@925
|
22 #include "base/AudioLevel.h"
|
Chris@923
|
23
|
Chris@923
|
24 #include <iostream>
|
Chris@926
|
25 #include <cmath>
|
Chris@940
|
26 #include <cassert>
|
Chris@923
|
27
|
Chris@923
|
28 using std::cerr;
|
Chris@923
|
29 using std::endl;
|
Chris@923
|
30
|
Chris@940
|
31 static const int maxLevel = 4; // min is 0, may be mute or not depending on m_includeMute
|
Chris@923
|
32 static const int maxPan = 2; // range is -maxPan to maxPan
|
Chris@923
|
33
|
Chris@923
|
34 LevelPanWidget::LevelPanWidget(QWidget *parent) :
|
Chris@923
|
35 QWidget(parent),
|
Chris@923
|
36 m_level(maxLevel),
|
Chris@923
|
37 m_pan(0),
|
Chris@940
|
38 m_editable(true),
|
Chris@940
|
39 m_includeMute(true)
|
Chris@923
|
40 {
|
Chris@923
|
41 }
|
Chris@923
|
42
|
Chris@923
|
43 LevelPanWidget::~LevelPanWidget()
|
Chris@923
|
44 {
|
Chris@923
|
45 }
|
Chris@923
|
46
|
Chris@929
|
47 QSize
|
Chris@929
|
48 LevelPanWidget::sizeHint() const
|
Chris@929
|
49 {
|
Chris@929
|
50 static double ratio = 0.0;
|
Chris@929
|
51 if (ratio == 0.0) {
|
Chris@929
|
52 double baseEm;
|
Chris@929
|
53 #ifdef Q_OS_MAC
|
Chris@929
|
54 baseEm = 17.0;
|
Chris@929
|
55 #else
|
Chris@929
|
56 baseEm = 15.0;
|
Chris@929
|
57 #endif
|
Chris@929
|
58 double em = QFontMetrics(QFont()).height();
|
Chris@929
|
59 ratio = em / baseEm;
|
Chris@929
|
60 }
|
Chris@929
|
61
|
Chris@929
|
62 int pixels = 40;
|
Chris@929
|
63 int scaled = int(pixels * ratio + 0.5);
|
Chris@929
|
64 if (pixels != 0 && scaled == 0) scaled = 1;
|
Chris@929
|
65 return QSize(scaled, scaled);
|
Chris@929
|
66 }
|
Chris@929
|
67
|
Chris@940
|
68 static int
|
Chris@940
|
69 db_to_level(double db)
|
Chris@940
|
70 {
|
Chris@940
|
71 // Only if !m_includeMute, otherwise AudioLevel is used.
|
Chris@940
|
72 // Levels are: +6 0 -6 -12 -20
|
Chris@940
|
73 assert(maxLevel == 4);
|
Chris@940
|
74 if (db > 3.) return 4;
|
Chris@940
|
75 else if (db > -3.) return 3;
|
Chris@940
|
76 else if (db > -9.) return 2;
|
Chris@940
|
77 else if (db > -16.) return 1;
|
Chris@940
|
78 else return 0;
|
Chris@940
|
79 }
|
Chris@940
|
80
|
Chris@940
|
81 static double
|
Chris@940
|
82 level_to_db(int level)
|
Chris@940
|
83 {
|
Chris@940
|
84 // Only if !m_includeMute, otherwise AudioLevel is used.
|
Chris@940
|
85 // Levels are: +6 0 -6 -12 -20
|
Chris@940
|
86 assert(maxLevel == 4);
|
Chris@940
|
87 if (level >= 4) return 6.;
|
Chris@940
|
88 else if (level == 3) return 0.;
|
Chris@940
|
89 else if (level == 2) return -6.;
|
Chris@940
|
90 else if (level == 1) return -12.;
|
Chris@940
|
91 else return -20.;
|
Chris@940
|
92 }
|
Chris@940
|
93
|
Chris@923
|
94 void
|
Chris@925
|
95 LevelPanWidget::setLevel(float flevel)
|
Chris@923
|
96 {
|
Chris@940
|
97 int level;
|
Chris@940
|
98 if (m_includeMute) {
|
Chris@940
|
99 level = AudioLevel::multiplier_to_fader
|
Chris@940
|
100 (flevel, maxLevel, AudioLevel::ShortFader);
|
Chris@940
|
101 } else {
|
Chris@940
|
102 level = db_to_level(AudioLevel::multiplier_to_dB(flevel));
|
Chris@940
|
103 }
|
Chris@925
|
104 if (level < 0) level = 0;
|
Chris@925
|
105 if (level > maxLevel) level = maxLevel;
|
Chris@925
|
106 if (level != m_level) {
|
Chris@925
|
107 m_level = level;
|
Chris@925
|
108 float convertsTo = getLevel();
|
Chris@925
|
109 if (fabsf(convertsTo - flevel) > 1e-5) {
|
Chris@925
|
110 emitLevelChanged();
|
Chris@925
|
111 }
|
Chris@925
|
112 update();
|
Chris@925
|
113 }
|
Chris@923
|
114 }
|
Chris@923
|
115
|
Chris@940
|
116 float
|
Chris@940
|
117 LevelPanWidget::getLevel() const
|
Chris@940
|
118 {
|
Chris@940
|
119 if (m_includeMute) {
|
Chris@940
|
120 return float(AudioLevel::fader_to_multiplier
|
Chris@940
|
121 (m_level, maxLevel, AudioLevel::ShortFader));
|
Chris@940
|
122 } else {
|
Chris@940
|
123 return float(AudioLevel::dB_to_multiplier(level_to_db(m_level)));
|
Chris@940
|
124 }
|
Chris@940
|
125 }
|
Chris@940
|
126
|
Chris@923
|
127 void
|
Chris@923
|
128 LevelPanWidget::setPan(float pan)
|
Chris@923
|
129 {
|
Chris@923
|
130 m_pan = int(round(pan * maxPan));
|
Chris@923
|
131 if (m_pan < -maxPan) m_pan = -maxPan;
|
Chris@923
|
132 if (m_pan > maxPan) m_pan = maxPan;
|
Chris@923
|
133 update();
|
Chris@923
|
134 }
|
Chris@923
|
135
|
Chris@940
|
136 bool
|
Chris@940
|
137 LevelPanWidget::isEditable() const
|
Chris@940
|
138 {
|
Chris@940
|
139 return m_editable;
|
Chris@940
|
140 }
|
Chris@940
|
141
|
Chris@940
|
142 bool
|
Chris@940
|
143 LevelPanWidget::includesMute() const
|
Chris@940
|
144 {
|
Chris@940
|
145 return m_includeMute;
|
Chris@940
|
146 }
|
Chris@940
|
147
|
Chris@923
|
148 void
|
Chris@923
|
149 LevelPanWidget::setEditable(bool editable)
|
Chris@923
|
150 {
|
Chris@923
|
151 m_editable = editable;
|
Chris@923
|
152 update();
|
Chris@923
|
153 }
|
Chris@923
|
154
|
Chris@940
|
155 void
|
Chris@940
|
156 LevelPanWidget::setIncludeMute(bool include)
|
Chris@923
|
157 {
|
Chris@940
|
158 m_includeMute = include;
|
Chris@940
|
159 emitLevelChanged();
|
Chris@940
|
160 update();
|
Chris@923
|
161 }
|
Chris@923
|
162
|
Chris@923
|
163 float
|
Chris@923
|
164 LevelPanWidget::getPan() const
|
Chris@923
|
165 {
|
Chris@923
|
166 return float(m_pan) / float(maxPan);
|
Chris@923
|
167 }
|
Chris@923
|
168
|
Chris@923
|
169 void
|
Chris@923
|
170 LevelPanWidget::emitLevelChanged()
|
Chris@923
|
171 {
|
Chris@923
|
172 cerr << "emitting levelChanged(" << getLevel() << ")" << endl;
|
Chris@923
|
173 emit levelChanged(getLevel());
|
Chris@923
|
174 }
|
Chris@923
|
175
|
Chris@923
|
176 void
|
Chris@923
|
177 LevelPanWidget::emitPanChanged()
|
Chris@923
|
178 {
|
Chris@923
|
179 cerr << "emitting panChanged(" << getPan() << ")" << endl;
|
Chris@923
|
180 emit panChanged(getPan());
|
Chris@923
|
181 }
|
Chris@923
|
182
|
Chris@923
|
183 void
|
Chris@923
|
184 LevelPanWidget::mousePressEvent(QMouseEvent *e)
|
Chris@923
|
185 {
|
Chris@923
|
186 mouseMoveEvent(e);
|
Chris@923
|
187 }
|
Chris@923
|
188
|
Chris@923
|
189 void
|
Chris@923
|
190 LevelPanWidget::mouseMoveEvent(QMouseEvent *e)
|
Chris@923
|
191 {
|
Chris@923
|
192 if (!m_editable) return;
|
Chris@923
|
193
|
Chris@923
|
194 int level, pan;
|
Chris@929
|
195 toCell(rect(), e->pos(), level, pan);
|
Chris@923
|
196 if (level == m_level && pan == m_pan) {
|
Chris@923
|
197 return;
|
Chris@923
|
198 }
|
Chris@923
|
199 if (level != m_level) {
|
Chris@923
|
200 m_level = level;
|
Chris@923
|
201 emitLevelChanged();
|
Chris@923
|
202 }
|
Chris@923
|
203 if (pan != m_pan) {
|
Chris@923
|
204 m_pan = pan;
|
Chris@923
|
205 emitPanChanged();
|
Chris@923
|
206 }
|
Chris@923
|
207 update();
|
Chris@923
|
208 }
|
Chris@923
|
209
|
Chris@923
|
210 void
|
Chris@923
|
211 LevelPanWidget::mouseReleaseEvent(QMouseEvent *e)
|
Chris@923
|
212 {
|
Chris@923
|
213 mouseMoveEvent(e);
|
Chris@923
|
214 }
|
Chris@923
|
215
|
Chris@923
|
216 void
|
Chris@923
|
217 LevelPanWidget::wheelEvent(QWheelEvent *e)
|
Chris@923
|
218 {
|
Chris@923
|
219 if (e->modifiers() & Qt::ControlModifier) {
|
Chris@923
|
220 if (e->delta() > 0) {
|
Chris@923
|
221 if (m_pan < maxPan) {
|
Chris@923
|
222 ++m_pan;
|
Chris@923
|
223 emitPanChanged();
|
Chris@923
|
224 update();
|
Chris@923
|
225 }
|
Chris@923
|
226 } else {
|
Chris@923
|
227 if (m_pan > -maxPan) {
|
Chris@923
|
228 --m_pan;
|
Chris@923
|
229 emitPanChanged();
|
Chris@923
|
230 update();
|
Chris@923
|
231 }
|
Chris@923
|
232 }
|
Chris@923
|
233 } else {
|
Chris@923
|
234 if (e->delta() > 0) {
|
Chris@923
|
235 if (m_level < maxLevel) {
|
Chris@923
|
236 ++m_level;
|
Chris@923
|
237 emitLevelChanged();
|
Chris@923
|
238 update();
|
Chris@923
|
239 }
|
Chris@923
|
240 } else {
|
Chris@923
|
241 if (m_level > 0) {
|
Chris@923
|
242 --m_level;
|
Chris@923
|
243 emitLevelChanged();
|
Chris@923
|
244 update();
|
Chris@923
|
245 }
|
Chris@923
|
246 }
|
Chris@923
|
247 }
|
Chris@923
|
248 }
|
Chris@923
|
249
|
Chris@923
|
250 void
|
Chris@929
|
251 LevelPanWidget::toCell(QRectF rect, QPointF loc, int &level, int &pan) const
|
Chris@923
|
252 {
|
Chris@929
|
253 double w = rect.width(), h = rect.height();
|
Chris@929
|
254
|
Chris@923
|
255 int npan = maxPan * 2 + 1;
|
Chris@924
|
256 int nlevel = maxLevel + 1;
|
Chris@929
|
257
|
Chris@924
|
258 double wcell = w / npan, hcell = h / nlevel;
|
Chris@929
|
259
|
Chris@932
|
260 level = int((h - (loc.y() - rect.y())) / hcell);
|
Chris@924
|
261 if (level < 0) level = 0;
|
Chris@923
|
262 if (level > maxLevel) level = maxLevel;
|
Chris@929
|
263
|
Chris@932
|
264 pan = int((loc.x() - rect.x()) / wcell) - maxPan;
|
Chris@923
|
265 if (pan < -maxPan) pan = -maxPan;
|
Chris@923
|
266 if (pan > maxPan) pan = maxPan;
|
Chris@923
|
267 }
|
Chris@923
|
268
|
Chris@923
|
269 QSizeF
|
Chris@929
|
270 LevelPanWidget::cellSize(QRectF rect) const
|
Chris@923
|
271 {
|
Chris@929
|
272 double w = rect.width(), h = rect.height();
|
Chris@923
|
273 int npan = maxPan * 2 + 1;
|
Chris@924
|
274 int nlevel = maxLevel + 1;
|
Chris@924
|
275 double wcell = w / npan, hcell = h / nlevel;
|
Chris@923
|
276 return QSizeF(wcell, hcell);
|
Chris@923
|
277 }
|
Chris@923
|
278
|
Chris@923
|
279 QPointF
|
Chris@929
|
280 LevelPanWidget::cellCentre(QRectF rect, int level, int pan) const
|
Chris@923
|
281 {
|
Chris@929
|
282 QSizeF cs = cellSize(rect);
|
Chris@932
|
283 return QPointF(rect.x() + cs.width() * (pan + maxPan) + cs.width() / 2.,
|
Chris@932
|
284 rect.y() + rect.height() - cs.height() * (level + 1) + cs.height() / 2.);
|
Chris@923
|
285 }
|
Chris@923
|
286
|
Chris@923
|
287 QSizeF
|
Chris@929
|
288 LevelPanWidget::cellLightSize(QRectF rect) const
|
Chris@923
|
289 {
|
Chris@923
|
290 double extent = 3. / 4.;
|
Chris@929
|
291 QSizeF cs = cellSize(rect);
|
Chris@923
|
292 double m = std::min(cs.width(), cs.height());
|
Chris@923
|
293 return QSizeF(m * extent, m * extent);
|
Chris@923
|
294 }
|
Chris@923
|
295
|
Chris@923
|
296 QRectF
|
Chris@929
|
297 LevelPanWidget::cellLightRect(QRectF rect, int level, int pan) const
|
Chris@923
|
298 {
|
Chris@929
|
299 QSizeF cls = cellLightSize(rect);
|
Chris@929
|
300 QPointF cc = cellCentre(rect, level, pan);
|
Chris@923
|
301 return QRectF(cc.x() - cls.width() / 2.,
|
Chris@923
|
302 cc.y() - cls.height() / 2.,
|
Chris@923
|
303 cls.width(),
|
Chris@923
|
304 cls.height());
|
Chris@923
|
305 }
|
Chris@923
|
306
|
Chris@923
|
307 double
|
Chris@929
|
308 LevelPanWidget::thinLineWidth(QRectF rect) const
|
Chris@923
|
309 {
|
Chris@929
|
310 double tw = ceil(rect.width() / (maxPan * 2. * 10.));
|
Chris@929
|
311 double th = ceil(rect.height() / (maxLevel * 10.));
|
Chris@923
|
312 return std::min(th, tw);
|
Chris@923
|
313 }
|
Chris@923
|
314
|
Chris@941
|
315 static QColor
|
Chris@941
|
316 level_to_colour(int level)
|
Chris@941
|
317 {
|
Chris@941
|
318 assert(maxLevel == 4);
|
Chris@941
|
319 if (level == 0) return Qt::black;
|
Chris@941
|
320 else if (level == 1) return QColor(80, 0, 0);
|
Chris@941
|
321 else if (level == 2) return QColor(160, 0, 0);
|
Chris@941
|
322 else if (level == 3) return QColor(255, 0, 0);
|
Chris@941
|
323 else return QColor(255, 255, 0);
|
Chris@941
|
324 }
|
Chris@941
|
325
|
Chris@923
|
326 void
|
Chris@929
|
327 LevelPanWidget::renderTo(QPaintDevice *dev, QRectF rect, bool asIfEditable) const
|
Chris@923
|
328 {
|
Chris@929
|
329 QPainter paint(dev);
|
Chris@923
|
330
|
Chris@923
|
331 paint.setRenderHint(QPainter::Antialiasing, true);
|
Chris@923
|
332
|
Chris@923
|
333 QPen pen;
|
Chris@923
|
334
|
Chris@929
|
335 double thin = thinLineWidth(rect);
|
Chris@938
|
336
|
Chris@923
|
337 pen.setColor(QColor(127, 127, 127, 127));
|
Chris@929
|
338 pen.setWidthF(cellLightSize(rect).width() + thin);
|
Chris@923
|
339 pen.setCapStyle(Qt::RoundCap);
|
Chris@923
|
340 paint.setPen(pen);
|
Chris@923
|
341
|
Chris@923
|
342 for (int pan = -maxPan; pan <= maxPan; ++pan) {
|
Chris@929
|
343 paint.drawLine(cellCentre(rect, 0, pan), cellCentre(rect, maxLevel, pan));
|
Chris@924
|
344 }
|
Chris@924
|
345
|
Chris@924
|
346 if (isEnabled()) {
|
Chris@924
|
347 pen.setColor(Qt::black);
|
Chris@924
|
348 } else {
|
Chris@924
|
349 pen.setColor(Qt::darkGray);
|
Chris@923
|
350 }
|
Chris@929
|
351
|
Chris@940
|
352 if (!asIfEditable && m_includeMute && m_level == 0) {
|
Chris@929
|
353 pen.setWidthF(thin * 2);
|
Chris@929
|
354 pen.setCapStyle(Qt::RoundCap);
|
Chris@929
|
355 paint.setPen(pen);
|
Chris@930
|
356 paint.drawLine(cellCentre(rect, 0, -maxPan),
|
Chris@930
|
357 cellCentre(rect, maxLevel, maxPan));
|
Chris@930
|
358 paint.drawLine(cellCentre(rect, maxLevel, -maxPan),
|
Chris@930
|
359 cellCentre(rect, 0, maxPan));
|
Chris@929
|
360 return;
|
Chris@929
|
361 }
|
Chris@923
|
362
|
Chris@923
|
363 pen.setWidthF(thin);
|
Chris@923
|
364 pen.setCapStyle(Qt::FlatCap);
|
Chris@923
|
365 paint.setPen(pen);
|
Chris@923
|
366
|
Chris@924
|
367 for (int level = 0; level <= m_level; ++level) {
|
Chris@924
|
368 if (isEnabled()) {
|
Chris@941
|
369 paint.setBrush(level_to_colour(level));
|
Chris@924
|
370 }
|
Chris@929
|
371 QRectF clr = cellLightRect(rect, level, m_pan);
|
Chris@940
|
372 if (m_includeMute && m_level == 0) {
|
Chris@924
|
373 paint.drawLine(clr.topLeft(), clr.bottomRight());
|
Chris@924
|
374 paint.drawLine(clr.bottomLeft(), clr.topRight());
|
Chris@924
|
375 } else {
|
Chris@924
|
376 paint.drawEllipse(clr);
|
Chris@924
|
377 }
|
Chris@923
|
378 }
|
Chris@923
|
379 }
|
Chris@923
|
380
|
Chris@929
|
381 void
|
Chris@929
|
382 LevelPanWidget::paintEvent(QPaintEvent *)
|
Chris@929
|
383 {
|
Chris@929
|
384 renderTo(this, rect(), m_editable);
|
Chris@929
|
385 }
|
Chris@923
|
386
|
Chris@929
|
387
|
Chris@929
|
388
|