1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2
3 /*
4 Sonic Visualiser
5 An audio file viewer and annotation editor.
6 Centre for Digital Music, Queen Mary, University of London.
7 This file copyright 2006 QMUL.
8
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License as
11 published by the Free Software Foundation; either version 2 of the
12 License, or (at your option) any later version. See the file
13 COPYING included with this distribution for more information.
14 */
15
16 #include "RangeMapper.h"
17 #include "system/System.h"
18
19 #include <cassert>
20 #include <cmath>
21
22 #include <iostream>
23 #include <stdexcept>
24
LinearRangeMapper(int minpos,int maxpos,double minval,double maxval,QString unit,bool inverted,std::map<int,QString> labels)25 LinearRangeMapper::LinearRangeMapper(int minpos, int maxpos,
26 double minval, double maxval,
27 QString unit, bool inverted,
28 std::map<int, QString> labels) :
29 m_minpos(minpos),
30 m_maxpos(maxpos),
31 m_minval(minval),
32 m_maxval(maxval),
33 m_unit(unit),
34 m_inverted(inverted),
35 m_labels(labels)
36 {
37 if (m_maxval == m_minval) {
38 throw std::logic_error("LinearRangeMapper: maxval must differ from minval");
39 }
40 if (m_maxpos == m_minpos) {
41 throw std::logic_error("LinearRangeMapper: maxpos must differ from minpos");
42 }
43 }
44
45 int
getPositionForValue(double value) const46 LinearRangeMapper::getPositionForValue(double value) const
47 {
48 int position = getPositionForValueUnclamped(value);
49 if (position < m_minpos) position = m_minpos;
50 if (position > m_maxpos) position = m_maxpos;
51 return position;
52 }
53
54 int
getPositionForValueUnclamped(double value) const55 LinearRangeMapper::getPositionForValueUnclamped(double value) const
56 {
57 int position = m_minpos +
58 int(lrint(((value - m_minval) / (m_maxval - m_minval))
59 * (m_maxpos - m_minpos)));
60 if (m_inverted) return m_maxpos - (position - m_minpos);
61 else return position;
62 }
63
64 double
getValueForPosition(int position) const65 LinearRangeMapper::getValueForPosition(int position) const
66 {
67 if (position < m_minpos) position = m_minpos;
68 if (position > m_maxpos) position = m_maxpos;
69 double value = getValueForPositionUnclamped(position);
70 return value;
71 }
72
73 double
getValueForPositionUnclamped(int position) const74 LinearRangeMapper::getValueForPositionUnclamped(int position) const
75 {
76 if (m_inverted) position = m_maxpos - (position - m_minpos);
77 double value = m_minval +
78 ((double(position - m_minpos) / double(m_maxpos - m_minpos))
79 * (m_maxval - m_minval));
80 // cerr << "getValueForPositionUnclamped(" << position << "): minval " << m_minval << ", maxval " << m_maxval << ", value " << value << endl;
81 return value;
82 }
83
84 QString
getLabel(int position) const85 LinearRangeMapper::getLabel(int position) const
86 {
87 if (m_labels.find(position) != m_labels.end()) {
88 return m_labels.at(position);
89 } else {
90 return "";
91 }
92 }
93
LogRangeMapper(int minpos,int maxpos,double minval,double maxval,QString unit,bool inverted)94 LogRangeMapper::LogRangeMapper(int minpos, int maxpos,
95 double minval, double maxval,
96 QString unit, bool inverted) :
97 m_minpos(minpos),
98 m_maxpos(maxpos),
99 m_unit(unit),
100 m_inverted(inverted)
101 {
102 convertMinMax(minpos, maxpos, minval, maxval, m_minlog, m_ratio);
103
104 // cerr << "LogRangeMapper: minpos " << minpos << ", maxpos "
105 // << maxpos << ", minval " << minval << ", maxval "
106 // << maxval << ", minlog " << m_minlog << ", ratio " << m_ratio
107 // << ", unit " << unit << endl;
108
109 if (m_maxpos == m_minpos) {
110 throw std::logic_error("LogRangeMapper: maxpos must differ from minpos");
111 }
112
113 m_maxlog = (m_maxpos - m_minpos) / m_ratio + m_minlog;
114
115 // cerr << "LogRangeMapper: maxlog = " << m_maxlog << endl;
116 }
117
118 void
convertMinMax(int minpos,int maxpos,double minval,double maxval,double & minlog,double & ratio)119 LogRangeMapper::convertMinMax(int minpos, int maxpos,
120 double minval, double maxval,
121 double &minlog, double &ratio)
122 {
123 static double thresh = powf(10, -10);
124 if (minval < thresh) minval = thresh;
125 minlog = log10(minval);
126 ratio = (maxpos - minpos) / (log10(maxval) - minlog);
127 }
128
129 void
convertRatioMinLog(double ratio,double minlog,int minpos,int maxpos,double & minval,double & maxval)130 LogRangeMapper::convertRatioMinLog(double ratio, double minlog,
131 int minpos, int maxpos,
132 double &minval, double &maxval)
133 {
134 minval = pow(10, minlog);
135 maxval = pow(10, (maxpos - minpos) / ratio + minlog);
136 }
137
138 int
getPositionForValue(double value) const139 LogRangeMapper::getPositionForValue(double value) const
140 {
141 int position = getPositionForValueUnclamped(value);
142 if (position < m_minpos) position = m_minpos;
143 if (position > m_maxpos) position = m_maxpos;
144 return position;
145 }
146
147 int
getPositionForValueUnclamped(double value) const148 LogRangeMapper::getPositionForValueUnclamped(double value) const
149 {
150 static double thresh = pow(10, -10);
151 if (value < thresh) value = thresh;
152 int position = int(lrint((log10(value) - m_minlog) * m_ratio)) + m_minpos;
153 if (m_inverted) return m_maxpos - (position - m_minpos);
154 else return position;
155 }
156
157 double
getValueForPosition(int position) const158 LogRangeMapper::getValueForPosition(int position) const
159 {
160 if (position < m_minpos) position = m_minpos;
161 if (position > m_maxpos) position = m_maxpos;
162 double value = getValueForPositionUnclamped(position);
163 return value;
164 }
165
166 double
getValueForPositionUnclamped(int position) const167 LogRangeMapper::getValueForPositionUnclamped(int position) const
168 {
169 if (m_inverted) position = m_maxpos - (position - m_minpos);
170 double value = pow(10, (position - m_minpos) / m_ratio + m_minlog);
171 return value;
172 }
173
InterpolatingRangeMapper(CoordMap pointMappings,QString unit)174 InterpolatingRangeMapper::InterpolatingRangeMapper(CoordMap pointMappings,
175 QString unit) :
176 m_mappings(pointMappings),
177 m_unit(unit)
178 {
179 for (CoordMap::const_iterator i = m_mappings.begin();
180 i != m_mappings.end(); ++i) {
181 m_reverse[i->second] = i->first;
182 }
183 }
184
185 int
getPositionForValue(double value) const186 InterpolatingRangeMapper::getPositionForValue(double value) const
187 {
188 int pos = getPositionForValueUnclamped(value);
189 CoordMap::const_iterator i = m_mappings.begin();
190 if (pos < i->second) pos = i->second;
191 i = m_mappings.end(); --i;
192 if (pos > i->second) pos = i->second;
193 return pos;
194 }
195
196 int
getPositionForValueUnclamped(double value) const197 InterpolatingRangeMapper::getPositionForValueUnclamped(double value) const
198 {
199 double p = interpolate(&m_mappings, value);
200 return int(lrint(p));
201 }
202
203 double
getValueForPosition(int position) const204 InterpolatingRangeMapper::getValueForPosition(int position) const
205 {
206 double val = getValueForPositionUnclamped(position);
207 CoordMap::const_iterator i = m_mappings.begin();
208 if (val < i->first) val = i->first;
209 i = m_mappings.end(); --i;
210 if (val > i->first) val = i->first;
211 return val;
212 }
213
214 double
getValueForPositionUnclamped(int position) const215 InterpolatingRangeMapper::getValueForPositionUnclamped(int position) const
216 {
217 return interpolate(&m_reverse, position);
218 }
219
220 template <typename T>
221 double
interpolate(T * mapping,double value) const222 InterpolatingRangeMapper::interpolate(T *mapping, double value) const
223 {
224 // lower_bound: first element which does not compare less than value
225 typename T::const_iterator i =
226 mapping->lower_bound(typename T::key_type(value));
227
228 if (i == mapping->begin()) {
229 // value is less than or equal to first element, so use the
230 // gradient from first to second and extend it
231 ++i;
232 }
233
234 if (i == mapping->end()) {
235 // value is off the end, so use the gradient from penultimate
236 // to ultimate and extend it
237 --i;
238 }
239
240 typename T::const_iterator j = i;
241 --j;
242
243 double gradient = double(i->second - j->second) / double(i->first - j->first);
244
245 return j->second + (value - j->first) * gradient;
246 }
247
AutoRangeMapper(CoordMap pointMappings,QString unit)248 AutoRangeMapper::AutoRangeMapper(CoordMap pointMappings,
249 QString unit) :
250 m_mappings(pointMappings),
251 m_unit(unit)
252 {
253 m_type = chooseMappingTypeFor(m_mappings);
254
255 CoordMap::const_iterator first = m_mappings.begin();
256 CoordMap::const_iterator last = m_mappings.end();
257 --last;
258
259 switch (m_type) {
260 case StraightLine:
261 m_mapper = new LinearRangeMapper(first->second, last->second,
262 first->first, last->first,
263 unit, false);
264 break;
265 case Logarithmic:
266 m_mapper = new LogRangeMapper(first->second, last->second,
267 first->first, last->first,
268 unit, false);
269 break;
270 case Interpolating:
271 m_mapper = new InterpolatingRangeMapper(m_mappings, unit);
272 break;
273 }
274 }
275
~AutoRangeMapper()276 AutoRangeMapper::~AutoRangeMapper()
277 {
278 delete m_mapper;
279 }
280
281 AutoRangeMapper::MappingType
chooseMappingTypeFor(const CoordMap & mappings)282 AutoRangeMapper::chooseMappingTypeFor(const CoordMap &mappings)
283 {
284 // how do we work out whether a linear/log mapping is "close enough"?
285
286 CoordMap::const_iterator first = mappings.begin();
287 CoordMap::const_iterator last = mappings.end();
288 --last;
289
290 LinearRangeMapper linm(first->second, last->second,
291 first->first, last->first,
292 "", false);
293
294 bool inadequate = false;
295
296 for (CoordMap::const_iterator i = mappings.begin();
297 i != mappings.end(); ++i) {
298 int candidate = linm.getPositionForValue(i->first);
299 int diff = candidate - i->second;
300 if (diff < 0) diff = -diff;
301 if (diff > 1) {
302 // cerr << "AutoRangeMapper::chooseMappingTypeFor: diff = " << diff
303 // << ", straight-line mapping inadequate" << endl;
304 inadequate = true;
305 break;
306 }
307 }
308
309 if (!inadequate) {
310 return StraightLine;
311 }
312
313 LogRangeMapper logm(first->second, last->second,
314 first->first, last->first,
315 "", false);
316
317 inadequate = false;
318
319 for (CoordMap::const_iterator i = mappings.begin();
320 i != mappings.end(); ++i) {
321 int candidate = logm.getPositionForValue(i->first);
322 int diff = candidate - i->second;
323 if (diff < 0) diff = -diff;
324 if (diff > 1) {
325 // cerr << "AutoRangeMapper::chooseMappingTypeFor: diff = " << diff
326 // << ", log mapping inadequate" << endl;
327 inadequate = true;
328 break;
329 }
330 }
331
332 if (!inadequate) {
333 return Logarithmic;
334 }
335
336 return Interpolating;
337 }
338
339 int
getPositionForValue(double value) const340 AutoRangeMapper::getPositionForValue(double value) const
341 {
342 return m_mapper->getPositionForValue(value);
343 }
344
345 double
getValueForPosition(int position) const346 AutoRangeMapper::getValueForPosition(int position) const
347 {
348 return m_mapper->getValueForPosition(position);
349 }
350
351 int
getPositionForValueUnclamped(double value) const352 AutoRangeMapper::getPositionForValueUnclamped(double value) const
353 {
354 return m_mapper->getPositionForValueUnclamped(value);
355 }
356
357 double
getValueForPositionUnclamped(int position) const358 AutoRangeMapper::getValueForPositionUnclamped(int position) const
359 {
360 return m_mapper->getValueForPositionUnclamped(position);
361 }
362