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