annotate base/RangeMapper.cpp @ 1412:b7a9edee85e0 scale-ticks

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