changeset 885:12a6140b3ae0

Add unclamped range mapper methods
author Chris Cannam
date Thu, 06 Feb 2014 15:31:16 +0000
parents 633a8fa622c9
children 48410857b03c
files base/RangeMapper.cpp base/RangeMapper.h base/test/TestRangeMapper.h
diffstat 3 files changed, 194 insertions(+), 32 deletions(-) [+]
line wrap: on
line diff
--- a/base/RangeMapper.cpp	Thu Feb 06 10:14:59 2014 +0000
+++ b/base/RangeMapper.cpp	Thu Feb 06 15:31:16 2014 +0000
@@ -38,11 +38,18 @@
 int
 LinearRangeMapper::getPositionForValue(float value) const
 {
+    int position = getPositionForValueUnclamped(value);
+    if (position < m_minpos) position = m_minpos;
+    if (position > m_maxpos) position = m_maxpos;
+    return position;
+}
+
+int
+LinearRangeMapper::getPositionForValueUnclamped(float value) const
+{
     int position = m_minpos +
         lrintf(((value - m_minval) / (m_maxval - m_minval))
                * (m_maxpos - m_minpos));
-    if (position < m_minpos) position = m_minpos;
-    if (position > m_maxpos) position = m_maxpos;
     if (m_inverted) return m_maxpos - (position - m_minpos);
     else return position;
 }
@@ -50,9 +57,16 @@
 float
 LinearRangeMapper::getValueForPosition(int position) const
 {
-    if (m_inverted) position = m_maxpos - (position - m_minpos);
     if (position < m_minpos) position = m_minpos;
     if (position > m_maxpos) position = m_maxpos;
+    float value = getValueForPositionUnclamped(position);
+    return value;
+}
+
+float
+LinearRangeMapper::getValueForPositionUnclamped(int position) const
+{
+    if (m_inverted) position = m_maxpos - (position - m_minpos);
     float value = m_minval +
         ((float(position - m_minpos) / float(m_maxpos - m_minpos))
          * (m_maxval - m_minval));
@@ -104,11 +118,18 @@
 int
 LogRangeMapper::getPositionForValue(float value) const
 {
-    int position = lrintf((log10(value) - m_minlog) * m_ratio) + m_minpos;
+    int position = getPositionForValueUnclamped(value);
     if (position < m_minpos) position = m_minpos;
     if (position > m_maxpos) position = m_maxpos;
-//    cerr << "LogRangeMapper::getPositionForValue: " << value << " -> "
-//              << position << " (minpos " << m_minpos << ", maxpos " << m_maxpos << ", ratio " << m_ratio << ", minlog " << m_minlog << ")" << endl;
+    return position;
+}
+
+int
+LogRangeMapper::getPositionForValueUnclamped(float value) const
+{
+    static float thresh = powf(10, -10);
+    if (value < thresh) value = thresh;
+    int position = lrintf((log10(value) - m_minlog) * m_ratio) + m_minpos;
     if (m_inverted) return m_maxpos - (position - m_minpos);
     else return position;
 }
@@ -116,12 +137,17 @@
 float
 LogRangeMapper::getValueForPosition(int position) const
 {
-    if (m_inverted) position = m_maxpos - (position - m_minpos);
     if (position < m_minpos) position = m_minpos;
     if (position > m_maxpos) position = m_maxpos;
+    float value = getValueForPositionUnclamped(position);
+    return value;
+}
+
+float
+LogRangeMapper::getValueForPositionUnclamped(int position) const
+{
+    if (m_inverted) position = m_maxpos - (position - m_minpos);
     float value = powf(10, (position - m_minpos) / m_ratio + m_minlog);
-//    cerr << "LogRangeMapper::getValueForPosition: " << position << " -> "
-//              << value << " (minpos " << m_minpos << ", maxpos " << m_maxpos << ", ratio " << m_ratio << ", minlog " << m_minlog << ")" << endl;
     return value;
 }
 
@@ -139,31 +165,63 @@
 int
 InterpolatingRangeMapper::getPositionForValue(float value) const
 {
-    CoordMap::const_iterator i = m_mappings.lower_bound(value);
-    CoordMap::const_iterator j = i;
-    --j;
+    int pos = getPositionForValueUnclamped(value);
+    CoordMap::const_iterator i = m_mappings.begin();
+    if (pos < i->second) pos = i->second;
+    i = m_mappings.end(); --i;
+    if (pos > i->second) pos = i->second;
+    return pos;
+}
 
-    if (i == m_mappings.end()) return j->second;
-    if (i == m_mappings.begin()) return i->second;
-    if (i->first == value) return i->second;
-
-    return lrintf(((value - j->first) / (i->first - j->first)) *
-                  float(i->second - j->second) + j->second);
+int
+InterpolatingRangeMapper::getPositionForValueUnclamped(float value) const
+{
+    float p = interpolate(&m_mappings, value);
+    return lrintf(p);
 }
 
 float
 InterpolatingRangeMapper::getValueForPosition(int position) const
 {
-    std::map<int, float>::const_iterator i = m_reverse.lower_bound(position);
-    std::map<int, float>::const_iterator j = i;
+    float val = getValueForPositionUnclamped(position);
+    CoordMap::const_iterator i = m_mappings.begin();
+    if (val < i->first) val = i->first;
+    i = m_mappings.end(); --i;
+    if (val > i->first) val = i->first;
+    return val;
+}
+
+float
+InterpolatingRangeMapper::getValueForPositionUnclamped(int position) const
+{
+    return interpolate(&m_reverse, position);
+}
+
+template <typename T>
+float
+InterpolatingRangeMapper::interpolate(T *mapping, float value) const
+{
+    // lower_bound: first element which does not compare less than value
+    typename T::const_iterator i = mapping->lower_bound(value);
+
+    if (i == mapping->begin()) {
+        // value is less than or equal to first element, so use the
+        // gradient from first to second and extend it
+        ++i;
+    }
+
+    if (i == mapping->end()) {
+        // value is off the end, so use the gradient from penultimate
+        // to ultimate and extend it
+        --i;
+    }
+
+    typename T::const_iterator j = i;
     --j;
 
-    if (i == m_reverse.end()) return j->second;
-    if (i == m_reverse.begin()) return i->second;
-    if (i->first == position) return i->second;
+    float gradient = float(i->second - j->second) / float(i->first - j->first);
 
-    return ((float(position) - j->first) / (i->first - j->first)) *
-        (i->second - j->second) + j->second;
+    return j->second + (value - j->first) * gradient;
 }
 
 AutoRangeMapper::AutoRangeMapper(CoordMap pointMappings,
@@ -220,8 +278,8 @@
         int diff = candidate - i->second;
         if (diff < 0) diff = -diff;
         if (diff > 1) {
-            cerr << "AutoRangeMapper::chooseMappingTypeFor: diff = " << diff
-                 << ", straight-line mapping inadequate" << endl;
+//            cerr << "AutoRangeMapper::chooseMappingTypeFor: diff = " << diff
+//                 << ", straight-line mapping inadequate" << endl;
             inadequate = true;
             break;
         }
@@ -243,8 +301,8 @@
         int diff = candidate - i->second;
         if (diff < 0) diff = -diff;
         if (diff > 1) {
-            cerr << "AutoRangeMapper::chooseMappingTypeFor: diff = " << diff
-                 << ", log mapping inadequate" << endl;
+//            cerr << "AutoRangeMapper::chooseMappingTypeFor: diff = " << diff
+//                 << ", log mapping inadequate" << endl;
             inadequate = true;
             break;
         }
@@ -268,3 +326,15 @@
 {
     return m_mapper->getValueForPosition(position);
 }
+
+int
+AutoRangeMapper::getPositionForValueUnclamped(float value) const
+{
+    return m_mapper->getPositionForValueUnclamped(value);
+}
+
+float
+AutoRangeMapper::getValueForPositionUnclamped(int position) const
+{
+    return m_mapper->getValueForPositionUnclamped(position);
+}
--- a/base/RangeMapper.h	Thu Feb 06 10:14:59 2014 +0000
+++ b/base/RangeMapper.h	Thu Feb 06 15:31:16 2014 +0000
@@ -25,8 +25,42 @@
 {
 public:
     virtual ~RangeMapper() { }
+
+    /**
+     * Return the position that maps to the given value, rounding to
+     * the nearest position and clamping to the minimum and maximum
+     * extents of the mapper's positional range.
+     */
     virtual int getPositionForValue(float value) const = 0;
+
+    /**
+     * Return the position that maps to the given value, rounding to
+     * the nearest position, without clamping. That is, whatever
+     * mapping function is in use will be projected even outside the
+     * minimum and maximum extents of the mapper's positional
+     * range. (The mapping outside that range is not guaranteed to be
+     * exact, except if the mapper is a linear one.)
+     */
+    virtual int getPositionForValueUnclamped(float value) const = 0;
+
+    /**
+     * Return the value mapped from the given position, clamping to
+     * the minimum and maximum extents of the mapper's value range.
+     */
     virtual float getValueForPosition(int position) const = 0;
+
+    /**
+     * Return the value mapped from the given positionq, without
+     * clamping. That is, whatever mapping function is in use will be
+     * projected even outside the minimum and maximum extents of the
+     * mapper's value range. (The mapping outside that range is not
+     * guaranteed to be exact, except if the mapper is a linear one.)
+     */
+    virtual float getValueForPositionUnclamped(int position) const = 0;
+
+    /**
+     * Get the unit of the mapper's value range.
+     */
     virtual QString getUnit() const { return ""; }
 };
 
@@ -45,7 +79,10 @@
                       QString unit = "", bool inverted = false);
     
     virtual int getPositionForValue(float value) const;
+    virtual int getPositionForValueUnclamped(float value) const;
+
     virtual float getValueForPosition(int position) const;
+    virtual float getValueForPositionUnclamped(int position) const;
 
     virtual QString getUnit() const { return m_unit; }
 
@@ -64,9 +101,10 @@
     /**
      * Map values in range minval->maxval into integer range
      * minpos->maxpos such that logs of the values are mapped
-     * linearly. minval and minpos must be less than maxval and maxpos
-     * respectively. If inverted is true, the range will be mapped
-     * "backwards" (minval to maxpos and maxval to minpos).
+     * linearly. minval must be greater than zero, and minval and
+     * minpos must be less than maxval and maxpos respectively. If
+     * inverted is true, the range will be mapped "backwards" (minval
+     * to maxpos and maxval to minpos).
      */
     LogRangeMapper(int minpos, int maxpos,
                    float minval, float maxval,
@@ -81,7 +119,10 @@
                               float &ratio, float &minlog);
 
     virtual int getPositionForValue(float value) const;
+    virtual int getPositionForValueUnclamped(float value) const;
+
     virtual float getValueForPosition(int position) const;
+    virtual float getValueForPositionUnclamped(int position) const;
 
     virtual QString getUnit() const { return m_unit; }
 
@@ -122,7 +163,10 @@
                              QString unit);
 
     virtual int getPositionForValue(float value) const;
+    virtual int getPositionForValueUnclamped(float value) const;
+
     virtual float getValueForPosition(int position) const;
+    virtual float getValueForPositionUnclamped(int position) const;
 
     virtual QString getUnit() const { return m_unit; }
 
@@ -130,6 +174,9 @@
     CoordMap m_mappings;
     std::map<int, float> m_reverse;
     QString m_unit;
+
+    template <typename T>
+    float interpolate(T *mapping, float v) const;
 };
 
 class AutoRangeMapper : public RangeMapper
@@ -189,7 +236,10 @@
     MappingType getType() const { return m_type; }
 
     virtual int getPositionForValue(float value) const;
+    virtual int getPositionForValueUnclamped(float value) const;
+
     virtual float getValueForPosition(int position) const;
+    virtual float getValueForPositionUnclamped(int position) const;
 
     virtual QString getUnit() const { return m_unit; }
 
--- a/base/test/TestRangeMapper.h	Thu Feb 06 10:14:59 2014 +0000
+++ b/base/test/TestRangeMapper.h	Thu Feb 06 15:31:16 2014 +0000
@@ -42,6 +42,10 @@
 	QCOMPARE(rm.getPositionForValue(0.2), 1);
 	QCOMPARE(rm.getPositionForValue(-12), 1);
 	QCOMPARE(rm.getPositionForValue(6.1), 8);
+	QCOMPARE(rm.getPositionForValueUnclamped(3.0), 6);
+	QCOMPARE(rm.getPositionForValueUnclamped(0.2), 0);
+	QCOMPARE(rm.getPositionForValueUnclamped(-12), -24);
+	QCOMPARE(rm.getPositionForValueUnclamped(6.1), 12);
     }
 
     void linearDownForward()
@@ -56,6 +60,10 @@
 	QCOMPARE(rm.getPositionForValue(0.2), 8);
 	QCOMPARE(rm.getPositionForValue(-12), 8);
 	QCOMPARE(rm.getPositionForValue(6.1), 1);
+	QCOMPARE(rm.getPositionForValueUnclamped(3.0), 3);
+	QCOMPARE(rm.getPositionForValueUnclamped(0.2), 9);
+	QCOMPARE(rm.getPositionForValueUnclamped(-12), 33);
+	QCOMPARE(rm.getPositionForValueUnclamped(6.1), -3);
     }
 
     void linearUpBackward()
@@ -68,6 +76,10 @@
 	QCOMPARE(rm.getValueForPosition(7), 3.5);
 	QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(1));
 	QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(8));
+	QCOMPARE(rm.getValueForPositionUnclamped(6), 3.0);
+	QCOMPARE(rm.getValueForPositionUnclamped(0), 0.0);
+	QCOMPARE(rm.getValueForPositionUnclamped(-24), -12.0);
+	QCOMPARE(rm.getValueForPositionUnclamped(12), 6.0);
     }
 
     void linearDownBackward()
@@ -80,6 +92,10 @@
 	QCOMPARE(rm.getValueForPosition(2), 3.5);
 	QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(1));
 	QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(8));
+	QCOMPARE(rm.getValueForPositionUnclamped(3), 3.0);
+	QCOMPARE(rm.getValueForPositionUnclamped(9), 0.0);
+	QCOMPARE(rm.getValueForPositionUnclamped(33), -12.0);
+	QCOMPARE(rm.getValueForPositionUnclamped(-3), 6.0);
     }
 
     void logUpForward()
@@ -93,6 +109,9 @@
 	QCOMPARE(rm.getPositionForValue(1000.0), 5);
 	QCOMPARE(rm.getPositionForValue(900.0), 5);
 	QCOMPARE(rm.getPositionForValue(20000), 6);
+	QCOMPARE(rm.getPositionForValueUnclamped(1.0), 2);
+	QCOMPARE(rm.getPositionForValueUnclamped(1000000.0), 8);
+	QCOMPARE(rm.getPositionForValueUnclamped(1000.0), 5);
     }
 
     void logDownForward()
@@ -106,6 +125,9 @@
 	QCOMPARE(rm.getPositionForValue(1000.0), 5);
 	QCOMPARE(rm.getPositionForValue(900.0), 5);
 	QCOMPARE(rm.getPositionForValue(20000), 4);
+	QCOMPARE(rm.getPositionForValueUnclamped(1.0), 8);
+	QCOMPARE(rm.getPositionForValueUnclamped(1000000.0), 2);
+	QCOMPARE(rm.getPositionForValueUnclamped(1000.0), 5);
     }
 
     void logUpBackward()
@@ -118,6 +140,9 @@
 	QCOMPARE(rm.getValueForPosition(6), 10000.0);
 	QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(3));
 	QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(7));
+	QCOMPARE(rm.getValueForPositionUnclamped(2), 1.0);
+        QCOMPARE(rm.getValueForPositionUnclamped(8), 1000000.0);
+        QCOMPARE(rm.getValueForPositionUnclamped(5), 1000.0);
     }
 
     void logDownBackward()
@@ -130,6 +155,9 @@
 	QCOMPARE(rm.getValueForPosition(4), 10000.0);
 	QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(3));
 	QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(7));
+	QCOMPARE(rm.getValueForPositionUnclamped(8), 1.0);
+        QCOMPARE(rm.getValueForPositionUnclamped(2), 1000000.0);
+        QCOMPARE(rm.getValueForPositionUnclamped(5), 1000.0);
     }
 
     void interpolatingForward()
@@ -147,6 +175,9 @@
 	QCOMPARE(rm.getPositionForValue(3.0), 30);
 	QCOMPARE(rm.getPositionForValue(2.5), 25);
 	QCOMPARE(rm.getPositionForValue(4.5), 60);
+	QCOMPARE(rm.getPositionForValueUnclamped(0.0), 0);
+	QCOMPARE(rm.getPositionForValueUnclamped(2.5), 25);
+	QCOMPARE(rm.getPositionForValueUnclamped(6.0), 90);
     }
 
     void interpolatingBackward()
@@ -174,10 +205,15 @@
 	AutoRangeMapper rm1(mappings, "x");
 	QCOMPARE(rm1.getUnit(), QString("x"));
 	QCOMPARE(rm1.getType(), AutoRangeMapper::StraightLine);
+	QCOMPARE(rm1.getPositionForValue(0.1), 1);
 	QCOMPARE(rm1.getPositionForValue(0.5), 1);
 	QCOMPARE(rm1.getPositionForValue(4.0), 8);
+	QCOMPARE(rm1.getPositionForValue(4.5), 8);
 	QCOMPARE(rm1.getPositionForValue(3.0), 6);
 	QCOMPARE(rm1.getPositionForValue(3.1), 6);
+	QCOMPARE(rm1.getPositionForValueUnclamped(0.1), 0);
+	QCOMPARE(rm1.getPositionForValueUnclamped(3.1), 6);
+	QCOMPARE(rm1.getPositionForValueUnclamped(4.5), 9);
 	mappings[3.0] = 6;
 	mappings[3.5] = 7;
 	AutoRangeMapper rm2(mappings, "x");
@@ -205,6 +241,9 @@
 	QCOMPARE(rm1.getPositionForValue(1000.0), 5);
 	QCOMPARE(rm1.getPositionForValue(900.0), 5);
 	QCOMPARE(rm1.getPositionForValue(20000), 6);
+	QCOMPARE(rm1.getPositionForValueUnclamped(1.0), 2);
+	QCOMPARE(rm1.getPositionForValueUnclamped(900.0), 5);
+	QCOMPARE(rm1.getPositionForValueUnclamped(1000000.0), 8);
 	mappings[100] = 4;
 	AutoRangeMapper rm2(mappings, "x");
 	QCOMPARE(rm2.getUnit(), QString("x"));
@@ -234,6 +273,9 @@
 	QCOMPARE(rm.getPositionForValue(3.0), 30);
 	QCOMPARE(rm.getPositionForValue(2.5), 25);
 	QCOMPARE(rm.getPositionForValue(4.5), 60);
+	QCOMPARE(rm.getPositionForValueUnclamped(0.0), 0);
+	QCOMPARE(rm.getPositionForValueUnclamped(5.0), 70);
+	QCOMPARE(rm.getPositionForValueUnclamped(6.0), 90);
     }
 };