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 #ifndef SV_RANGE_MAPPER_H
17 #define SV_RANGE_MAPPER_H
18 
19 #include <QString>
20 
21 #include "Debug.h"
22 #include <map>
23 
24 class RangeMapper
25 {
26 public:
27     virtual ~RangeMapper() { }
28 
29     /**
30      * Return the position that maps to the given value, rounding to
31      * the nearest position and clamping to the minimum and maximum
32      * extents of the mapper's positional range.
33      */
34     virtual int getPositionForValue(double value) const = 0;
35 
36     /**
37      * Return the position that maps to the given value, rounding to
38      * the nearest position, without clamping. That is, whatever
39      * mapping function is in use will be projected even outside the
40      * minimum and maximum extents of the mapper's positional
41      * range. (The mapping outside that range is not guaranteed to be
42      * exact, except if the mapper is a linear one.)
43      */
44     virtual int getPositionForValueUnclamped(double value) const = 0;
45 
46     /**
47      * Return the value mapped from the given position, clamping to
48      * the minimum and maximum extents of the mapper's value range.
49      */
50     virtual double getValueForPosition(int position) const = 0;
51 
52     /**
53      * Return the value mapped from the given position, without
54      * clamping. That is, whatever mapping function is in use will be
55      * projected even outside the minimum and maximum extents of the
56      * mapper's value range. (The mapping outside that range is not
57      * guaranteed to be exact, except if the mapper is a linear one.)
58      */
59     virtual double getValueForPositionUnclamped(int position) const = 0;
60 
61     /**
62      * Get the unit of the mapper's value range.
63      */
64     virtual QString getUnit() const { return ""; }
65 
66     /**
67      * The mapper may optionally provide special labels for one or
68      * more individual positions (such as the minimum position, the
69      * default, or indeed all positions). These should be used in any
70      * display context in preference to just showing the numerical
71      * value for the position. If a position has such a label, return
72      * it here.
73      */
74     virtual QString getLabel(int /* position */) const { return ""; }
75 };
76 
77 
78 class LinearRangeMapper : public RangeMapper
79 {
80 public:
81     /**
82      * Map values in range minval->maxval linearly into integer range
83      * minpos->maxpos. minval and minpos must be less than maxval and
84      * maxpos respectively. If inverted is true, the range will be
85      * mapped "backwards" (minval to maxpos and maxval to minpos).
86      */
87     LinearRangeMapper(int minpos, int maxpos,
88                       double minval, double maxval,
89                       QString unit = "", bool inverted = false,
90                       std::map<int, QString> labels = {});
91 
92     int getPositionForValue(double value) const override;
93     int getPositionForValueUnclamped(double value) const override;
94 
95     double getValueForPosition(int position) const override;
96     double getValueForPositionUnclamped(int position) const override;
97 
98     QString getUnit() const override { return m_unit; }
99     QString getLabel(int position) const override;
100 
101 protected:
102     int m_minpos;
103     int m_maxpos;
104     double m_minval;
105     double m_maxval;
106     QString m_unit;
107     bool m_inverted;
108     std::map<int, QString> m_labels;
109 };
110 
111 class LogRangeMapper : public RangeMapper
112 {
113 public:
114     /**
115      * Map values in range minval->maxval into integer range
116      * minpos->maxpos such that logs of the values are mapped
117      * linearly. minval must be greater than zero, and minval and
118      * minpos must be less than maxval and maxpos respectively. If
119      * inverted is true, the range will be mapped "backwards" (minval
120      * to maxpos and maxval to minpos).
121      */
122     LogRangeMapper(int minpos, int maxpos,
123                    double minval, double maxval,
124                    QString m_unit = "", bool inverted = false);
125 
126     static void convertRatioMinLog(double ratio, double minlog,
127                                    int minpos, int maxpos,
128                                    double &minval, double &maxval);
129 
130     static void convertMinMax(int minpos, int maxpos,
131                               double minval, double maxval,
132                               double &minlog, double &ratio);
133 
134     int getPositionForValue(double value) const override;
135     int getPositionForValueUnclamped(double value) const override;
136 
137     double getValueForPosition(int position) const override;
138     double getValueForPositionUnclamped(int position) const override;
139 
140     QString getUnit() const override { return m_unit; }
141 
142 protected:
143     int m_minpos;
144     int m_maxpos;
145     double m_ratio;
146     double m_minlog;
147     double m_maxlog;
148     QString m_unit;
149     bool m_inverted;
150 };
151 
152 class InterpolatingRangeMapper : public RangeMapper
153 {
154 public:
155     typedef std::map<double, int> CoordMap;
156 
157     /**
158      * Given a series of (value, position) coordinate mappings,
159      * construct a range mapper that maps arbitrary values, in the
160      * range between minimum and maximum of the provided values, onto
161      * coordinates using linear interpolation between the supplied
162      * points.
163      *
164      *!!! todo: Cubic -- more generally useful than linear interpolation
165      *!!! todo: inverted flag
166      *
167      * The set of provided mappings must contain at least two
168      * coordinates.
169      *
170      * It is expected that the values and positions in the coordinate
171      * mappings will both be monotonically increasing (i.e. no
172      * inflections in the mapping curve). Behaviour is undefined if
173      * this is not the case.
174      */
175     InterpolatingRangeMapper(CoordMap pointMappings,
176                              QString unit);
177 
178     int getPositionForValue(double value) const override;
179     int getPositionForValueUnclamped(double value) const override;
180 
181     double getValueForPosition(int position) const override;
182     double getValueForPositionUnclamped(int position) const override;
183 
184     QString getUnit() const override { return m_unit; }
185 
186 protected:
187     CoordMap m_mappings;
188     std::map<int, double> m_reverse;
189     QString m_unit;
190 
191     template <typename T>
192     double interpolate(T *mapping, double v) const;
193 };
194 
195 class AutoRangeMapper : public RangeMapper
196 {
197 public:
198     enum MappingType {
199         Interpolating,
200         StraightLine,
201         Logarithmic,
202     };
203 
204     typedef std::map<double, int> CoordMap;
205 
206     /**
207      * Given a series of (value, position) coordinate mappings,
208      * construct a range mapper that maps arbitrary values, in the
209      * range between minimum and maximum of the provided values, onto
210      * coordinates.
211      *
212      * The mapping used may be
213      *
214      *    Interpolating -- an InterpolatingRangeMapper will be used
215      *
216      *    StraightLine -- a LinearRangeMapper from the minimum to
217      *    maximum value coordinates will be used, ignoring all other
218      *    supplied coordinate mappings
219      *
220      *    Logarithmic -- a LogRangeMapper from the minimum to
221      *    maximum value coordinates will be used, ignoring all other
222      *    supplied coordinate mappings
223      *
224      * The mapping will be chosen automatically by looking at the
225      * supplied coordinates. If the supplied coordinates fall on a
226      * straight line, a StraightLine mapping will be used; if they
227      * fall on a log curve, a Logarithmic mapping will be used;
228      * otherwise an Interpolating mapping will be used.
229      *
230      *!!! todo: inverted flag
231      *
232      * The set of provided mappings must contain at least two
233      * coordinates, or at least three if the points are not supposed
234      * to be in a straight line.
235      *
236      * It is expected that the values and positions in the coordinate
237      * mappings will both be monotonically increasing (i.e. no
238      * inflections in the mapping curve). Behaviour is undefined if
239      * this is not the case.
240      */
241     AutoRangeMapper(CoordMap pointMappings,
242                     QString unit);
243 
244     ~AutoRangeMapper();
245 
246     /**
247      * Return the mapping type in use.
248      */
249     MappingType getType() const { return m_type; }
250 
251     int getPositionForValue(double value) const override;
252     int getPositionForValueUnclamped(double value) const override;
253 
254     double getValueForPosition(int position) const override;
255     double getValueForPositionUnclamped(int position) const override;
256 
257     QString getUnit() const override { return m_unit; }
258 
259 protected:
260     MappingType m_type;
261     CoordMap m_mappings;
262     QString m_unit;
263     RangeMapper *m_mapper;
264 
265     MappingType chooseMappingTypeFor(const CoordMap &);
266 };
267 
268 #endif
269