Chris@1068
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@1068
|
2
|
Chris@1068
|
3 /*
|
Chris@1068
|
4 Sonic Visualiser
|
Chris@1068
|
5 An audio file viewer and annotation editor.
|
Chris@1068
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@1068
|
7 This file copyright 2006-2016 Chris Cannam and QMUL.
|
Chris@1068
|
8
|
Chris@1068
|
9 This program is free software; you can redistribute it and/or
|
Chris@1068
|
10 modify it under the terms of the GNU General Public License as
|
Chris@1068
|
11 published by the Free Software Foundation; either version 2 of the
|
Chris@1068
|
12 License, or (at your option) any later version. See the file
|
Chris@1068
|
13 COPYING included with this distribution for more information.
|
Chris@1068
|
14 */
|
Chris@1068
|
15
|
Chris@1068
|
16 #include "ColourScale.h"
|
Chris@1068
|
17
|
Chris@1068
|
18 #include "base/AudioLevel.h"
|
Chris@1068
|
19 #include "base/LogRange.h"
|
Chris@1068
|
20
|
Chris@1068
|
21 #include <cmath>
|
Chris@1129
|
22 #include <iostream>
|
Chris@1129
|
23
|
Chris@1129
|
24 using namespace std;
|
Chris@1068
|
25
|
Chris@1068
|
26 int ColourScale::m_maxPixel = 255;
|
Chris@1068
|
27
|
Chris@1070
|
28 ColourScale::ColourScale(Parameters parameters) :
|
Chris@1070
|
29 m_params(parameters),
|
Chris@1070
|
30 m_mapper(m_params.colourMap, 1.f, double(m_maxPixel))
|
Chris@1068
|
31 {
|
Chris@1070
|
32 if (m_params.minValue >= m_params.maxValue) {
|
Chris@1129
|
33 cerr << "ERROR: ColourScale::ColourScale: minValue = "
|
Chris@1129
|
34 << m_params.minValue << ", maxValue = " << m_params.maxValue << endl;
|
Chris@1068
|
35 throw std::logic_error("maxValue must be greater than minValue");
|
Chris@1068
|
36 }
|
Chris@1068
|
37
|
Chris@1070
|
38 m_mappedMin = m_params.minValue;
|
Chris@1070
|
39 m_mappedMax = m_params.maxValue;
|
Chris@1068
|
40
|
Chris@1127
|
41 if (m_mappedMin < m_params.threshold) {
|
Chris@1127
|
42 m_mappedMin = m_params.threshold;
|
Chris@1127
|
43 }
|
Chris@1127
|
44
|
Chris@1137
|
45 if (m_params.scaleType == ColourScaleType::Log) {
|
Chris@1068
|
46
|
Chris@1253
|
47 // When used in e.g. spectrogram, we have a range with a min
|
Chris@1253
|
48 // value of zero. The LogRange converts that to a threshold
|
Chris@1253
|
49 // value of -10, so for a range of e.g. (0,1) we end up with
|
Chris@1253
|
50 // (-10,0) as the mapped range.
|
Chris@1253
|
51 //
|
Chris@1253
|
52 // But in other contexts we could end up with a mapped range
|
Chris@1253
|
53 // much larger than that if we have a small non-zero minimum
|
Chris@1253
|
54 // value (less than 1e-10), or a particularly large
|
Chris@1253
|
55 // maximum. That's unlikely to give us good results, so let's
|
Chris@1253
|
56 // insist that the mapped log range has no more than 10
|
Chris@1253
|
57 // difference between min and max, to match the behaviour when
|
Chris@1253
|
58 // min == 0 at the input.
|
Chris@1253
|
59 //
|
Chris@1253
|
60 double threshold = -10.0;
|
Chris@1253
|
61 LogRange::mapRange(m_mappedMin, m_mappedMax, threshold);
|
Chris@1253
|
62 if (m_mappedMin < m_mappedMax + threshold) {
|
Chris@1253
|
63 m_mappedMin = m_mappedMax + threshold;
|
Chris@1253
|
64 }
|
Chris@1068
|
65
|
Chris@1137
|
66 } else if (m_params.scaleType == ColourScaleType::PlusMinusOne) {
|
Chris@1068
|
67
|
Chris@1068
|
68 m_mappedMin = -1.0;
|
Chris@1068
|
69 m_mappedMax = 1.0;
|
Chris@1068
|
70
|
Chris@1137
|
71 } else if (m_params.scaleType == ColourScaleType::Absolute) {
|
Chris@1068
|
72
|
Chris@1068
|
73 m_mappedMin = fabs(m_mappedMin);
|
Chris@1068
|
74 m_mappedMax = fabs(m_mappedMax);
|
Chris@1068
|
75 if (m_mappedMin >= m_mappedMax) {
|
Chris@1068
|
76 std::swap(m_mappedMin, m_mappedMax);
|
Chris@1068
|
77 }
|
Chris@1068
|
78 }
|
Chris@1068
|
79
|
Chris@1068
|
80 if (m_mappedMin >= m_mappedMax) {
|
Chris@1129
|
81 cerr << "ERROR: ColourScale::ColourScale: minValue = " << m_params.minValue
|
Chris@1129
|
82 << ", maxValue = " << m_params.maxValue
|
Chris@1129
|
83 << ", threshold = " << m_params.threshold
|
Chris@1137
|
84 << ", scale = " << int(m_params.scaleType)
|
Chris@1129
|
85 << " resulting in mapped minValue = " << m_mappedMin
|
Chris@1129
|
86 << ", mapped maxValue = " << m_mappedMax << endl;
|
Chris@1068
|
87 throw std::logic_error("maxValue must be greater than minValue [after mapping]");
|
Chris@1068
|
88 }
|
Chris@1068
|
89 }
|
Chris@1068
|
90
|
Chris@1071
|
91 ColourScale::~ColourScale()
|
Chris@1071
|
92 {
|
Chris@1071
|
93 }
|
Chris@1071
|
94
|
Chris@1105
|
95 ColourScaleType
|
Chris@1079
|
96 ColourScale::getScale() const
|
Chris@1079
|
97 {
|
Chris@1137
|
98 return m_params.scaleType;
|
Chris@1079
|
99 }
|
Chris@1079
|
100
|
Chris@1068
|
101 int
|
Chris@1079
|
102 ColourScale::getPixel(double value) const
|
Chris@1068
|
103 {
|
Chris@1068
|
104 double maxPixF = m_maxPixel;
|
Chris@1068
|
105
|
Chris@1137
|
106 if (m_params.scaleType == ColourScaleType::Phase) {
|
Chris@1068
|
107 double half = (maxPixF - 1.f) / 2.f;
|
Chris@1138
|
108 int pixel = 1 + int((value * half) / M_PI + half);
|
Chris@1143
|
109 // cerr << "phase = " << value << " pixel = " << pixel << endl;
|
Chris@1138
|
110 return pixel;
|
Chris@1068
|
111 }
|
Chris@1068
|
112
|
Chris@1070
|
113 value *= m_params.gain;
|
Chris@1137
|
114
|
Chris@1070
|
115 if (value < m_params.threshold) return 0;
|
Chris@1068
|
116
|
Chris@1068
|
117 double mapped = value;
|
Chris@1068
|
118
|
Chris@1137
|
119 if (m_params.scaleType == ColourScaleType::Log) {
|
Chris@1068
|
120 mapped = LogRange::map(value);
|
Chris@1137
|
121 } else if (m_params.scaleType == ColourScaleType::PlusMinusOne) {
|
Chris@1068
|
122 if (mapped < -1.f) mapped = -1.f;
|
Chris@1068
|
123 if (mapped > 1.f) mapped = 1.f;
|
Chris@1137
|
124 } else if (m_params.scaleType == ColourScaleType::Absolute) {
|
Chris@1068
|
125 if (mapped < 0.f) mapped = -mapped;
|
Chris@1068
|
126 }
|
Chris@1137
|
127
|
Chris@1137
|
128 mapped *= m_params.multiple;
|
Chris@1137
|
129
|
Chris@1068
|
130 if (mapped < m_mappedMin) {
|
Chris@1068
|
131 mapped = m_mappedMin;
|
Chris@1068
|
132 }
|
Chris@1068
|
133 if (mapped > m_mappedMax) {
|
Chris@1068
|
134 mapped = m_mappedMax;
|
Chris@1068
|
135 }
|
Chris@1068
|
136
|
Chris@1068
|
137 double proportion = (mapped - m_mappedMin) / (m_mappedMax - m_mappedMin);
|
Chris@1068
|
138
|
Chris@1068
|
139 int pixel = 0;
|
Chris@1068
|
140
|
Chris@1137
|
141 if (m_params.scaleType == ColourScaleType::Meter) {
|
Chris@1068
|
142 pixel = AudioLevel::multiplier_to_preview(proportion, m_maxPixel-1) + 1;
|
Chris@1068
|
143 } else {
|
Chris@1068
|
144 pixel = int(proportion * maxPixF) + 1;
|
Chris@1068
|
145 }
|
Chris@1068
|
146
|
Chris@1070
|
147 if (pixel < 0) {
|
Chris@1070
|
148 pixel = 0;
|
Chris@1070
|
149 }
|
Chris@1070
|
150 if (pixel > m_maxPixel) {
|
Chris@1070
|
151 pixel = m_maxPixel;
|
Chris@1070
|
152 }
|
Chris@1068
|
153 return pixel;
|
Chris@1068
|
154 }
|
Chris@1068
|
155
|
Chris@1068
|
156 QColor
|
Chris@1079
|
157 ColourScale::getColourForPixel(int pixel, int rotation) const
|
Chris@1068
|
158 {
|
Chris@1068
|
159 if (pixel < 0) {
|
Chris@1068
|
160 pixel = 0;
|
Chris@1068
|
161 }
|
Chris@1068
|
162 if (pixel > m_maxPixel) {
|
Chris@1068
|
163 pixel = m_maxPixel;
|
Chris@1068
|
164 }
|
Chris@1068
|
165 if (pixel == 0) {
|
Chris@1068
|
166 if (m_mapper.hasLightBackground()) {
|
Chris@1068
|
167 return Qt::white;
|
Chris@1068
|
168 } else {
|
Chris@1068
|
169 return Qt::black;
|
Chris@1068
|
170 }
|
Chris@1068
|
171 } else {
|
Chris@1068
|
172 int target = int(pixel) + rotation;
|
Chris@1068
|
173 while (target < 1) target += m_maxPixel;
|
Chris@1068
|
174 while (target > m_maxPixel) target -= m_maxPixel;
|
Chris@1068
|
175 return m_mapper.map(double(target));
|
Chris@1068
|
176 }
|
Chris@1068
|
177 }
|