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