comparison widgets/LevelPanWidget.cpp @ 1301:e8368466fa34

Half-steps for level in level-pan widget
author Chris Cannam
date Thu, 21 Jun 2018 15:36:29 +0100
parents a34a2a25907c
children f3d3fab250ac
comparison
equal deleted inserted replaced
1300:1589bc7528b7 1301:e8368466fa34
28 #include <cassert> 28 #include <cassert>
29 29
30 using std::cerr; 30 using std::cerr;
31 using std::endl; 31 using std::endl;
32 32
33 static const int maxLevel = 4; // min is 0, may be mute or not depending on m_includeMute 33 /**
34 * Gain and pan scales:
35 *
36 * Gain: we have 5 circles vertically in the display, each of which
37 * has half-circle and full-circle versions, and we also have "no
38 * circles", so there are in total 11 distinct levels, which we refer
39 * to as "notches" and number 0-10. (We use "notch" because "level" is
40 * used by the external API to refer to audio gain.)
41 *
42 * i.e. the levels are represented by these (schematic, rotated to
43 * horizontal) displays:
44 *
45 * 0 X
46 * 1 [
47 * 2 []
48 * 3 [][
49 * ...
50 * 9 [][][][][
51 * 10 [][][][][]
52 *
53 * If we have mute enabled, then we map the range 0-10 to gain using
54 * AudioLevel::fader_to_* with the ShortFader type, which treats fader
55 * 0 as muted. If mute is disabled, then we map the range 1-10.
56 *
57 * We can also disable half-circles, which leaves the range unchanged
58 * but limits the notches to even values.
59 *
60 * Pan: we have 5 columns with no finer resolution, so we only have 2
61 * possible pan values on each side of centre.
62 */
63
34 static const int maxPan = 2; // range is -maxPan to maxPan 64 static const int maxPan = 2; // range is -maxPan to maxPan
35 65
36 LevelPanWidget::LevelPanWidget(QWidget *parent) : 66 LevelPanWidget::LevelPanWidget(QWidget *parent) :
37 QWidget(parent), 67 QWidget(parent),
38 m_level(maxLevel), 68 m_minNotch(0),
69 m_maxNotch(10),
70 m_notch(m_maxNotch),
39 m_pan(0), 71 m_pan(0),
40 m_monitorLeft(-1), 72 m_monitorLeft(-1),
41 m_monitorRight(-1), 73 m_monitorRight(-1),
42 m_editable(true), 74 m_editable(true),
43 m_editing(false), 75 m_editing(false),
44 m_includeMute(true) 76 m_includeMute(true),
77 m_includeHalfSteps(true)
45 { 78 {
46 setToolTip(tr("Drag vertically to adjust level, horizontally to adjust pan")); 79 setToolTip(tr("Drag vertically to adjust level, horizontally to adjust pan"));
47 setLevel(1.0); 80 setLevel(1.0);
48 setPan(0.0); 81 setPan(0.0);
49 } 82 }
65 LevelPanWidget::sizeHint() const 98 LevelPanWidget::sizeHint() const
66 { 99 {
67 return WidgetScale::scaleQSize(QSize(40, 40)); 100 return WidgetScale::scaleQSize(QSize(40, 40));
68 } 101 }
69 102
70 static int
71 db_to_level(double db)
72 {
73 // Only if !m_includeMute, otherwise AudioLevel is used.
74 // Levels are: +6 0 -6 -12 -20
75 assert(maxLevel == 4);
76 if (db > 3.) return 4;
77 else if (db > -3.) return 3;
78 else if (db > -9.) return 2;
79 else if (db > -16.) return 1;
80 else return 0;
81 }
82
83 static double
84 level_to_db(int level)
85 {
86 // Only if !m_includeMute, otherwise AudioLevel is used.
87 // Levels are: +6 0 -6 -12 -20
88 assert(maxLevel == 4);
89 if (level >= 4) return 6.;
90 else if (level == 3) return 0.;
91 else if (level == 2) return -6.;
92 else if (level == 1) return -12.;
93 else return -20.;
94 }
95
96 int 103 int
97 LevelPanWidget::audioLevelToLevel(float audioLevel, bool withMute) 104 LevelPanWidget::clampNotch(int notch) const
98 { 105 {
99 int level; 106 if (notch < m_minNotch) notch = m_minNotch;
100 if (withMute) { 107 if (notch > m_maxNotch) notch = m_maxNotch;
101 level = AudioLevel::multiplier_to_fader 108 if (!m_includeHalfSteps) {
102 (audioLevel, maxLevel, AudioLevel::ShortFader); 109 notch = (notch / 2) * 2;
103 } else { 110 }
104 level = db_to_level(AudioLevel::multiplier_to_dB(audioLevel)); 111 return notch;
105 } 112 }
106 if (level < 0) level = 0; 113
107 if (level > maxLevel) level = maxLevel; 114 int
108 return level; 115 LevelPanWidget::audioLevelToNotch(float audioLevel) const
116 {
117 int notch = AudioLevel::multiplier_to_fader
118 (audioLevel, m_maxNotch, AudioLevel::ShortFader);
119 return clampNotch(notch);
109 } 120 }
110 121
111 float 122 float
112 LevelPanWidget::levelToAudioLevel(int level, bool withMute) 123 LevelPanWidget::notchToAudioLevel(int notch) const
113 { 124 {
114 if (withMute) { 125 return float(AudioLevel::fader_to_multiplier
115 return float(AudioLevel::fader_to_multiplier 126 (notch, m_maxNotch, AudioLevel::ShortFader));
116 (level, maxLevel, AudioLevel::ShortFader)); 127 }
117 } else { 128
118 return float(AudioLevel::dB_to_multiplier(level_to_db(level))); 129 void
119 } 130 LevelPanWidget::setLevel(float level)
120 } 131 {
121 132 int notch = audioLevelToNotch(level);
122 void 133 if (notch != m_notch) {
123 LevelPanWidget::setLevel(float flevel) 134 m_notch = notch;
124 {
125 int level = audioLevelToLevel(flevel, m_includeMute);
126 if (level != m_level) {
127 m_level = level;
128 float convertsTo = getLevel(); 135 float convertsTo = getLevel();
129 if (fabsf(convertsTo - flevel) > 1e-5) { 136 if (fabsf(convertsTo - level) > 1e-5) {
130 emitLevelChanged(); 137 emitLevelChanged();
131 } 138 }
132 update(); 139 update();
133 } 140 }
141 SVCERR << "setLevel: level " << level << " -> notch " << m_notch << " (which converts back to level " << getLevel() << ")" << endl;
134 } 142 }
135 143
136 float 144 float
137 LevelPanWidget::getLevel() const 145 LevelPanWidget::getLevel() const
138 { 146 {
139 float flevel = levelToAudioLevel(m_level, m_includeMute); 147 return notchToAudioLevel(m_notch);
140 return flevel;
141 } 148 }
142 149
143 int 150 int
144 LevelPanWidget::audioPanToPan(float audioPan) 151 LevelPanWidget::audioPanToPan(float audioPan) const
145 { 152 {
146 int pan = int(round(audioPan * maxPan)); 153 int pan = int(round(audioPan * maxPan));
147 if (pan < -maxPan) pan = -maxPan; 154 if (pan < -maxPan) pan = -maxPan;
148 if (pan > maxPan) pan = maxPan; 155 if (pan > maxPan) pan = maxPan;
149 return pan; 156 return pan;
150 } 157 }
151 158
152 float 159 float
153 LevelPanWidget::panToAudioPan(int pan) 160 LevelPanWidget::panToAudioPan(int pan) const
154 { 161 {
155 return float(pan) / float(maxPan); 162 return float(pan) / float(maxPan);
156 } 163 }
157 164
158 void 165 void
200 207
201 void 208 void
202 LevelPanWidget::setIncludeMute(bool include) 209 LevelPanWidget::setIncludeMute(bool include)
203 { 210 {
204 m_includeMute = include; 211 m_includeMute = include;
212 if (m_includeMute) {
213 m_minNotch = 0;
214 } else {
215 m_minNotch = 1;
216 }
205 emitLevelChanged(); 217 emitLevelChanged();
206 update(); 218 update();
207 } 219 }
208 220
209 void 221 void
242 LevelPanWidget::mouseMoveEvent(QMouseEvent *e) 254 LevelPanWidget::mouseMoveEvent(QMouseEvent *e)
243 { 255 {
244 if (!m_editable) return; 256 if (!m_editable) return;
245 if (!m_editing) return; 257 if (!m_editing) return;
246 258
247 int level, pan; 259 int notch = coordsToNotch(rect(), e->pos());
248 toCell(rect(), e->pos(), level, pan); 260 int pan = coordsToPan(rect(), e->pos());
249 if (level == m_level && pan == m_pan) { 261
262 if (notch == m_notch && pan == m_pan) {
250 return; 263 return;
251 } 264 }
252 if (level != m_level) { 265 if (notch != m_notch) {
253 m_level = level; 266 m_notch = notch;
254 emitLevelChanged(); 267 emitLevelChanged();
255 } 268 }
256 if (pan != m_pan) { 269 if (pan != m_pan) {
257 m_pan = pan; 270 m_pan = pan;
258 emitPanChanged(); 271 emitPanChanged();
277 update(); 290 update();
278 } 291 }
279 } 292 }
280 } else { 293 } else {
281 if (e->delta() > 0) { 294 if (e->delta() > 0) {
282 if (m_level < maxLevel) { 295 if (m_notch < m_maxNotch) {
283 ++m_level; 296 ++m_notch;
284 emitLevelChanged(); 297 emitLevelChanged();
285 update(); 298 update();
286 } 299 }
287 } else { 300 } else {
288 if (m_level > 0) { 301 if (m_notch > m_minNotch) {
289 --m_level; 302 --m_notch;
290 emitLevelChanged(); 303 emitLevelChanged();
291 update(); 304 update();
292 } 305 }
293 } 306 }
294 } 307 }
295 } 308 }
296 309
297 void 310 int
298 LevelPanWidget::toCell(QRectF rect, QPointF loc, int &level, int &pan) const 311 LevelPanWidget::coordsToNotch(QRectF rect, QPointF loc) const
299 { 312 {
300 double w = rect.width(), h = rect.height(); 313 double h = rect.height();
314
315 int nnotch = m_maxNotch + 1;
316 double cell = h / nnotch;
317
318 int notch = int((h - (loc.y() - rect.y())) / cell);
319 notch = clampNotch(notch);
320
321 return notch;
322 }
323
324 int
325 LevelPanWidget::coordsToPan(QRectF rect, QPointF loc) const
326 {
327 double w = rect.width();
301 328
302 int npan = maxPan * 2 + 1; 329 int npan = maxPan * 2 + 1;
303 int nlevel = maxLevel + 1; 330 double cell = w / npan;
304 331
305 double wcell = w / npan, hcell = h / nlevel; 332 int pan = int((loc.x() - rect.x()) / cell) - maxPan;
306
307 level = int((h - (loc.y() - rect.y())) / hcell);
308 if (level < 0) level = 0;
309 if (level > maxLevel) level = maxLevel;
310
311 pan = int((loc.x() - rect.x()) / wcell) - maxPan;
312 if (pan < -maxPan) pan = -maxPan; 333 if (pan < -maxPan) pan = -maxPan;
313 if (pan > maxPan) pan = maxPan; 334 if (pan > maxPan) pan = maxPan;
335
336 return pan;
314 } 337 }
315 338
316 QSizeF 339 QSizeF
317 LevelPanWidget::cellSize(QRectF rect) const 340 LevelPanWidget::cellSize(QRectF rect) const
318 { 341 {
319 double w = rect.width(), h = rect.height(); 342 double w = rect.width(), h = rect.height();
320 int npan = maxPan * 2 + 1; 343 int ncol = maxPan * 2 + 1;
321 int nlevel = maxLevel + 1; 344 int nrow = m_maxNotch/2;
322 double wcell = w / npan, hcell = h / nlevel; 345 double wcell = w / ncol, hcell = h / nrow;
323 return QSizeF(wcell, hcell); 346 return QSizeF(wcell, hcell);
324 } 347 }
325 348
326 QPointF 349 QPointF
327 LevelPanWidget::cellCentre(QRectF rect, int level, int pan) const 350 LevelPanWidget::cellCentre(QRectF rect, int row, int col) const
328 { 351 {
329 QSizeF cs = cellSize(rect); 352 QSizeF cs = cellSize(rect);
330 return QPointF(rect.x() + cs.width() * (pan + maxPan) + cs.width() / 2., 353 return QPointF(rect.x() +
331 rect.y() + rect.height() - cs.height() * (level + 1) + cs.height() / 2.); 354 cs.width() * (col + maxPan) + cs.width() / 2.,
355 rect.y() + rect.height() -
356 cs.height() * (row + 1) + cs.height() / 2.);
332 } 357 }
333 358
334 QSizeF 359 QSizeF
335 LevelPanWidget::cellLightSize(QRectF rect) const 360 LevelPanWidget::cellLightSize(QRectF rect) const
336 { 361 {
339 double m = std::min(cs.width(), cs.height()); 364 double m = std::min(cs.width(), cs.height());
340 return QSizeF(m * extent, m * extent); 365 return QSizeF(m * extent, m * extent);
341 } 366 }
342 367
343 QRectF 368 QRectF
344 LevelPanWidget::cellLightRect(QRectF rect, int level, int pan) const 369 LevelPanWidget::cellLightRect(QRectF rect, int row, int col) const
345 { 370 {
346 QSizeF cls = cellLightSize(rect); 371 QSizeF cls = cellLightSize(rect);
347 QPointF cc = cellCentre(rect, level, pan); 372 QPointF cc = cellCentre(rect, row, col);
348 return QRectF(cc.x() - cls.width() / 2., 373 return QRectF(cc.x() - cls.width() / 2.,
349 cc.y() - cls.height() / 2., 374 cc.y() - cls.height() / 2.,
350 cls.width(), 375 cls.width(),
351 cls.height()); 376 cls.height());
352 } 377 }
353 378
354 double 379 double
355 LevelPanWidget::thinLineWidth(QRectF rect) const 380 LevelPanWidget::thinLineWidth(QRectF rect) const
356 { 381 {
357 double tw = ceil(rect.width() / (maxPan * 2. * 10.)); 382 double tw = ceil(rect.width() / (maxPan * 2. * 10.));
358 double th = ceil(rect.height() / (maxLevel * 10.)); 383 double th = ceil(rect.height() / (m_maxNotch/2 * 10.));
359 return std::min(th, tw); 384 return std::min(th, tw);
360 } 385 }
361 386
362 static QColor 387 QRectF
363 level_to_colour(int level) 388 LevelPanWidget::cellOutlineRect(QRectF rect, int row, int col) const
364 { 389 {
365 assert(maxLevel == 4); 390 QRectF clr = cellLightRect(rect, row, col);
366 if (level == 0) return Qt::black; 391 double adj = thinLineWidth(rect)/2;
367 else if (level == 1) return QColor(80, 0, 0); 392 return clr.adjusted(-adj, -adj, adj, adj);
368 else if (level == 2) return QColor(160, 0, 0); 393 }
369 else if (level == 3) return QColor(255, 0, 0); 394
370 else return QColor(255, 255, 0); 395 QColor
396 LevelPanWidget::notchToColour(int notch) const
397 {
398 if (notch < 3) return Qt::black;
399 if (notch < 5) return QColor(80, 0, 0);
400 if (notch < 7) return QColor(160, 0, 0);
401 if (notch < 9) return QColor(255, 0, 0);
402 return QColor(255, 255, 0);
371 } 403 }
372 404
373 void 405 void
374 LevelPanWidget::renderTo(QPaintDevice *dev, QRectF rect, bool asIfEditable) const 406 LevelPanWidget::renderTo(QPaintDevice *dev, QRectF rect, bool asIfEditable) const
375 { 407 {
379 411
380 QPen pen; 412 QPen pen;
381 413
382 double thin = thinLineWidth(rect); 414 double thin = thinLineWidth(rect);
383 415
384 pen.setColor(QColor(127, 127, 127, 127)); 416 QColor columnBackground = QColor(180, 180, 180);
417 pen.setColor(columnBackground);
385 pen.setWidthF(cellLightSize(rect).width() + thin); 418 pen.setWidthF(cellLightSize(rect).width() + thin);
386 pen.setCapStyle(Qt::RoundCap); 419 pen.setCapStyle(Qt::RoundCap);
387 paint.setPen(pen); 420 paint.setPen(pen);
388 paint.setBrush(Qt::NoBrush); 421 paint.setBrush(Qt::NoBrush);
389 422
390 for (int pan = -maxPan; pan <= maxPan; ++pan) { 423 for (int pan = -maxPan; pan <= maxPan; ++pan) {
391 paint.drawLine(cellCentre(rect, 0, pan), cellCentre(rect, maxLevel, pan)); 424 paint.drawLine(cellCentre(rect, 0, pan),
392 } 425 cellCentre(rect, m_maxNotch/2 - 1, pan));
393 426 }
394 if (m_monitorLeft > 0.f || m_monitorRight > 0.f) { 427
428 bool monitoring = (m_monitorLeft > 0.f || m_monitorRight > 0.f);
429
430 if (isEnabled()) {
431 pen.setColor(Qt::black);
432 } else {
433 pen.setColor(Qt::darkGray);
434 }
435
436 if (!asIfEditable && m_includeMute && m_notch == 0) {
437 // The X for mute takes up the whole display when we're not
438 // being rendered in editable style
439 pen.setWidthF(thin * 2);
440 pen.setCapStyle(Qt::RoundCap);
441 paint.setPen(pen);
442 paint.drawLine(cellCentre(rect, 0, -maxPan),
443 cellCentre(rect, m_maxNotch/2 - 1, maxPan));
444 paint.drawLine(cellCentre(rect, m_maxNotch/2 - 1, -maxPan),
445 cellCentre(rect, 0, maxPan));
446 } else {
447 // the normal case
448
449 // pen a bit less thin than in theory, so that we can erase
450 // semi-circles later without leaving a faint edge
451 pen.setWidthF(thin * 0.8);
452 pen.setCapStyle(Qt::FlatCap);
453 paint.setPen(pen);
454
455 if (m_includeMute && m_notch == 0) {
456 QRectF clr = cellLightRect(rect, 0, m_pan);
457 paint.drawLine(clr.topLeft(), clr.bottomRight());
458 paint.drawLine(clr.bottomLeft(), clr.topRight());
459 } else {
460 for (int notch = 1; notch <= m_notch; notch += 2) {
461 if (isEnabled() && !monitoring) {
462 paint.setBrush(notchToColour(notch));
463 }
464 QRectF clr = cellLightRect(rect, notch/2, m_pan);
465 paint.drawEllipse(clr);
466 }
467 if (m_notch % 2 != 0) {
468 QRectF clr = cellOutlineRect(rect, (m_notch-1)/2, m_pan);
469 paint.save();
470 paint.setPen(Qt::NoPen);
471 paint.setBrush(columnBackground);
472 paint.drawPie(clr, 0, 180 * 16);
473 paint.restore();
474 }
475 }
476 }
477
478 if (monitoring) {
395 paint.setPen(Qt::NoPen); 479 paint.setPen(Qt::NoPen);
396 for (int pan = -maxPan; pan <= maxPan; ++pan) { 480 for (int pan = -maxPan; pan <= maxPan; ++pan) {
397 float audioPan = panToAudioPan(pan); 481 float audioPan = panToAudioPan(pan);
398 float audioLevel; 482 float audioLevel;
399 if (audioPan < 0.f) { 483 if (audioPan < 0.f) {
400 audioLevel = m_monitorLeft + m_monitorRight * (1.f + audioPan); 484 audioLevel = m_monitorLeft + m_monitorRight * (1.f + audioPan);
401 } else { 485 } else {
402 audioLevel = m_monitorRight + m_monitorLeft * (1.f - audioPan); 486 audioLevel = m_monitorRight + m_monitorLeft * (1.f - audioPan);
403 } 487 }
404 int levelHere = audioLevelToLevel(audioLevel, false); 488 int notchHere = audioLevelToNotch(audioLevel);
405 for (int level = 0; level <= levelHere; ++level) { 489 for (int notch = 1; notch <= notchHere; notch += 2) {
406 paint.setBrush(level_to_colour(level)); 490 paint.setBrush(notchToColour(notch));
407 QRectF clr = cellLightRect(rect, level, pan); 491 QRectF clr = cellLightRect(rect, (notch-1)/2, pan);
408 paint.drawEllipse(clr); 492 double adj = thinLineWidth(rect)/2;
493 clr = clr.adjusted(adj, adj, -adj, -adj);
494 if (notch + 2 > notchHere && notchHere % 2 != 0) {
495 paint.drawPie(clr, 180 * 16, 180 * 16);
496 } else {
497 paint.drawEllipse(clr);
498 }
409 } 499 }
410 } 500 }
411 paint.setPen(pen); 501 paint.setPen(pen);
412 paint.setBrush(Qt::NoBrush); 502 paint.setBrush(Qt::NoBrush);
413 } 503 }
414
415 if (isEnabled()) {
416 pen.setColor(Qt::black);
417 } else {
418 pen.setColor(Qt::darkGray);
419 }
420
421 if (!asIfEditable && m_includeMute && m_level == 0) {
422 pen.setWidthF(thin * 2);
423 pen.setCapStyle(Qt::RoundCap);
424 paint.setPen(pen);
425 paint.drawLine(cellCentre(rect, 0, -maxPan),
426 cellCentre(rect, maxLevel, maxPan));
427 paint.drawLine(cellCentre(rect, maxLevel, -maxPan),
428 cellCentre(rect, 0, maxPan));
429 return;
430 }
431
432 pen.setWidthF(thin);
433 pen.setCapStyle(Qt::FlatCap);
434 paint.setPen(pen);
435
436 for (int level = 0; level <= m_level; ++level) {
437 if (isEnabled()) {
438 paint.setBrush(level_to_colour(level));
439 }
440 QRectF clr = cellLightRect(rect, level, m_pan);
441 if (m_includeMute && m_level == 0) {
442 paint.drawLine(clr.topLeft(), clr.bottomRight());
443 paint.drawLine(clr.bottomLeft(), clr.topRight());
444 } else {
445 paint.drawEllipse(clr);
446 }
447 }
448 } 504 }
449 505
450 void 506 void
451 LevelPanWidget::paintEvent(QPaintEvent *) 507 LevelPanWidget::paintEvent(QPaintEvent *)
452 { 508 {