]> git.sesse.net Git - nageru/blob - nonlinear_fader.cpp
Make NonlinearFader work with fixed 0..1000 range, not 0..height().
[nageru] / nonlinear_fader.cpp
1 #include <assert.h>
2 #include <math.h>
3 #include <stdio.h>
4 #include <vector>
5 #include <utility>
6
7 #include <QResizeEvent>
8 #include <QPainter>
9 #include <QStyle>
10 #include <QStyleOptionSlider>
11
12 #include "nonlinear_fader.h"
13
14 using namespace std;
15
16 namespace {
17
18 vector<pair<double, double>> fader_control_points = {
19         // The main area is from +6 to -12 dB (18 dB), and we use half the slider range for it.
20         { 6.0, 1.0 },
21         { -12.0, 0.5 },
22
23         // -12 to -21 is half the range (9 dB). Halve.
24         { -21.0, 0.325 },
25
26         // -21 to -30 (9 dB) gets the same range as the previous one.
27         { -30.0, 0.25 },
28
29         // -30 to -48 (18 dB) gets half of half.
30         { -48.0, 0.125 },
31
32         // -48 to -84 (36 dB) gets half of half of half.
33         { -84.0, 0.0 },
34 };
35
36 double slider_fraction_to_db(double db)
37 {
38         if (db >= fader_control_points[0].second) {
39                 return fader_control_points[0].first;
40         }
41         if (db <= fader_control_points.back().second) {
42                 return fader_control_points.back().first;
43         }
44         for (unsigned i = 1; i < fader_control_points.size(); ++i) {
45                 const double x0 = fader_control_points[i].second;
46                 const double x1 = fader_control_points[i - 1].second;
47                 const double y0 = fader_control_points[i].first;
48                 const double y1 = fader_control_points[i - 1].first;
49                 if (db >= x0 && db <= x1) {
50                         const double t = (db - x0) / (x1 - x0);
51                         return y0 + t * (y1 - y0);
52                 }
53         }
54         assert(false);
55 }
56
57 double db_to_slider_fraction(double x)
58 {
59         if (x >= fader_control_points[0].first) {
60                 return fader_control_points[0].second;
61         }
62         if (x <= fader_control_points.back().first) {
63                 return fader_control_points.back().second;
64         }
65         for (unsigned i = 1; i < fader_control_points.size(); ++i) {
66                 const double x0 = fader_control_points[i].first;
67                 const double x1 = fader_control_points[i - 1].first;
68                 const double y0 = fader_control_points[i].second;
69                 const double y1 = fader_control_points[i - 1].second;
70                 if (x >= x0 && x <= x1) {
71                         const double t = (x - x0) / (x1 - x0);
72                         return y0 + t * (y1 - y0);
73                 }
74         }
75         assert(false);
76 }
77
78 }  // namespace
79
80 NonLinearFader::NonLinearFader(QWidget *parent)
81         : QSlider(parent)
82 {
83         update_slider_position();
84 }
85
86 void NonLinearFader::setDbValue(double db)
87 {
88         db_value = db;
89         update_slider_position();
90         emit dbValueChanged(db);
91 }
92
93 void NonLinearFader::paintEvent(QPaintEvent *event)
94 {
95         QStyleOptionSlider opt;
96         this->initStyleOption(&opt);
97         QRect gr = this->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, this);
98         QRect sr = this->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);
99
100         // FIXME: Where does the slider_length / 2 come from? I can't really find it
101         // in the Qt code, but it seems to match up with reality.
102         int slider_length = sr.height();
103         int slider_max = gr.top() + (slider_length / 2);
104         int slider_min = gr.bottom() + (slider_length / 2) - slider_length + 1;
105
106         QPainter p(this);
107
108         // Draw some ticks every 6 dB.
109         // FIXME: Find a way to make the slider wider, so that we have more space for tickmarks
110         // and some dB numbering.
111         int x_margin = 5;
112         p.setPen(Qt::darkGray);
113         for (int db = -84; db <= 6; db += 6) {
114                 int y = slider_min + lrint(db_to_slider_fraction(db) * (slider_max - slider_min));
115                 p.drawLine(QPoint(0, y), QPoint(gr.left() - x_margin, y));
116                 p.drawLine(QPoint(gr.right() + x_margin, y), QPoint(width() - 1, y));
117         }
118
119         QSlider::paintEvent(event);
120 }
121
122 void NonLinearFader::sliderChange(SliderChange change)
123 {
124         QSlider::sliderChange(change);
125         if (change == QAbstractSlider::SliderValueChange && !inhibit_updates) {
126                 if (value() == 0) {
127                         db_value = -HUGE_VAL;
128                 } else {
129                         double frac = double(value() - minimum()) / (maximum() - minimum());
130                         db_value = slider_fraction_to_db(frac);
131                 }
132                 emit dbValueChanged(db_value);
133         }
134 }
135
136 void NonLinearFader::update_slider_position()
137 {
138         inhibit_updates = true;
139         double val = db_to_slider_fraction(db_value) * (maximum() - minimum()) + minimum();
140         setValue(lrint(val));
141         inhibit_updates = false;
142 }