annotate base/RangeMapper.cpp @ 1700:c1208b211d8c single-point

Ensure test fails rather than crashing if this reader doesn't get created
author Chris Cannam <cannam@all-day-breakfast.com>
date Fri, 03 May 2019 15:02:09 +0100
parents b89705af7a60
children
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@1545 23 #include <stdexcept>
Chris@189 24
Chris@189 25 LinearRangeMapper::LinearRangeMapper(int minpos, int maxpos,
Chris@1429 26 double minval, double maxval,
Chris@1203 27 QString unit, bool inverted,
Chris@1203 28 std::map<int, QString> labels) :
Chris@189 29 m_minpos(minpos),
Chris@189 30 m_maxpos(maxpos),
Chris@189 31 m_minval(minval),
Chris@189 32 m_maxval(maxval),
Chris@464 33 m_unit(unit),
Chris@1203 34 m_inverted(inverted),
Chris@1203 35 m_labels(labels)
Chris@189 36 {
Chris@1545 37 if (m_maxval == m_minval) {
Chris@1545 38 throw std::logic_error("LinearRangeMapper: maxval must differ from minval");
Chris@1545 39 }
Chris@1545 40 if (m_maxpos == m_minpos) {
Chris@1545 41 throw std::logic_error("LinearRangeMapper: maxpos must differ from minpos");
Chris@1545 42 }
Chris@189 43 }
Chris@189 44
Chris@189 45 int
Chris@1038 46 LinearRangeMapper::getPositionForValue(double value) const
Chris@189 47 {
Chris@885 48 int position = getPositionForValueUnclamped(value);
Chris@885 49 if (position < m_minpos) position = m_minpos;
Chris@885 50 if (position > m_maxpos) position = m_maxpos;
Chris@885 51 return position;
Chris@885 52 }
Chris@885 53
Chris@885 54 int
Chris@1038 55 LinearRangeMapper::getPositionForValueUnclamped(double value) const
Chris@885 56 {
Chris@190 57 int position = m_minpos +
Chris@1038 58 int(lrint(((value - m_minval) / (m_maxval - m_minval))
Chris@1038 59 * (m_maxpos - m_minpos)));
Chris@879 60 if (m_inverted) return m_maxpos - (position - m_minpos);
Chris@464 61 else return position;
Chris@189 62 }
Chris@189 63
Chris@1038 64 double
Chris@189 65 LinearRangeMapper::getValueForPosition(int position) const
Chris@189 66 {
Chris@879 67 if (position < m_minpos) position = m_minpos;
Chris@879 68 if (position > m_maxpos) position = m_maxpos;
Chris@1038 69 double value = getValueForPositionUnclamped(position);
Chris@885 70 return value;
Chris@885 71 }
Chris@885 72
Chris@1038 73 double
Chris@885 74 LinearRangeMapper::getValueForPositionUnclamped(int position) const
Chris@885 75 {
Chris@885 76 if (m_inverted) position = m_maxpos - (position - m_minpos);
Chris@1038 77 double value = m_minval +
Chris@1038 78 ((double(position - m_minpos) / double(m_maxpos - m_minpos))
Chris@190 79 * (m_maxval - m_minval));
Chris@1201 80 // cerr << "getValueForPositionUnclamped(" << position << "): minval " << m_minval << ", maxval " << m_maxval << ", value " << value << endl;
Chris@189 81 return value;
Chris@189 82 }
Chris@189 83
Chris@1203 84 QString
Chris@1203 85 LinearRangeMapper::getLabel(int position) const
Chris@1203 86 {
Chris@1203 87 if (m_labels.find(position) != m_labels.end()) {
Chris@1203 88 return m_labels.at(position);
Chris@1203 89 } else {
Chris@1203 90 return "";
Chris@1203 91 }
Chris@1203 92 }
Chris@1203 93
Chris@189 94 LogRangeMapper::LogRangeMapper(int minpos, int maxpos,
Chris@1038 95 double minval, double maxval,
Chris@464 96 QString unit, bool inverted) :
Chris@189 97 m_minpos(minpos),
Chris@189 98 m_maxpos(maxpos),
Chris@464 99 m_unit(unit),
Chris@464 100 m_inverted(inverted)
Chris@189 101 {
Chris@356 102 convertMinMax(minpos, maxpos, minval, maxval, m_minlog, m_ratio);
Chris@356 103
Chris@879 104 // cerr << "LogRangeMapper: minpos " << minpos << ", maxpos "
Chris@879 105 // << maxpos << ", minval " << minval << ", maxval "
Chris@879 106 // << maxval << ", minlog " << m_minlog << ", ratio " << m_ratio
Chris@879 107 // << ", unit " << unit << endl;
Chris@356 108
Chris@1545 109 if (m_maxpos == m_minpos) {
Chris@1545 110 throw std::logic_error("LogRangeMapper: maxpos must differ from minpos");
Chris@1545 111 }
Chris@189 112
Chris@189 113 m_maxlog = (m_maxpos - m_minpos) / m_ratio + m_minlog;
Chris@879 114
Chris@879 115 // cerr << "LogRangeMapper: maxlog = " << m_maxlog << endl;
Chris@189 116 }
Chris@189 117
Chris@356 118 void
Chris@356 119 LogRangeMapper::convertMinMax(int minpos, int maxpos,
Chris@1038 120 double minval, double maxval,
Chris@1038 121 double &minlog, double &ratio)
Chris@356 122 {
Chris@1038 123 static double thresh = powf(10, -10);
Chris@356 124 if (minval < thresh) minval = thresh;
Chris@1038 125 minlog = log10(minval);
Chris@1038 126 ratio = (maxpos - minpos) / (log10(maxval) - minlog);
Chris@356 127 }
Chris@356 128
Chris@356 129 void
Chris@1038 130 LogRangeMapper::convertRatioMinLog(double ratio, double minlog,
Chris@356 131 int minpos, int maxpos,
Chris@1038 132 double &minval, double &maxval)
Chris@356 133 {
Chris@1038 134 minval = pow(10, minlog);
Chris@1038 135 maxval = pow(10, (maxpos - minpos) / ratio + minlog);
Chris@356 136 }
Chris@356 137
Chris@189 138 int
Chris@1038 139 LogRangeMapper::getPositionForValue(double value) const
Chris@189 140 {
Chris@885 141 int position = getPositionForValueUnclamped(value);
Chris@189 142 if (position < m_minpos) position = m_minpos;
Chris@189 143 if (position > m_maxpos) position = m_maxpos;
Chris@885 144 return position;
Chris@885 145 }
Chris@885 146
Chris@885 147 int
Chris@1038 148 LogRangeMapper::getPositionForValueUnclamped(double value) const
Chris@885 149 {
Chris@1038 150 static double thresh = pow(10, -10);
Chris@885 151 if (value < thresh) value = thresh;
Chris@1038 152 int position = int(lrint((log10(value) - m_minlog) * m_ratio)) + m_minpos;
Chris@879 153 if (m_inverted) return m_maxpos - (position - m_minpos);
Chris@464 154 else return position;
Chris@189 155 }
Chris@189 156
Chris@1038 157 double
Chris@189 158 LogRangeMapper::getValueForPosition(int position) const
Chris@189 159 {
Chris@879 160 if (position < m_minpos) position = m_minpos;
Chris@879 161 if (position > m_maxpos) position = m_maxpos;
Chris@1038 162 double value = getValueForPositionUnclamped(position);
Chris@885 163 return value;
Chris@885 164 }
Chris@885 165
Chris@1038 166 double
Chris@885 167 LogRangeMapper::getValueForPositionUnclamped(int position) const
Chris@885 168 {
Chris@885 169 if (m_inverted) position = m_maxpos - (position - m_minpos);
Chris@1038 170 double value = pow(10, (position - m_minpos) / m_ratio + m_minlog);
Chris@189 171 return value;
Chris@189 172 }
Chris@189 173
Chris@880 174 InterpolatingRangeMapper::InterpolatingRangeMapper(CoordMap pointMappings,
Chris@880 175 QString unit) :
Chris@880 176 m_mappings(pointMappings),
Chris@880 177 m_unit(unit)
Chris@880 178 {
Chris@880 179 for (CoordMap::const_iterator i = m_mappings.begin();
Chris@880 180 i != m_mappings.end(); ++i) {
Chris@880 181 m_reverse[i->second] = i->first;
Chris@880 182 }
Chris@880 183 }
Chris@880 184
Chris@880 185 int
Chris@1038 186 InterpolatingRangeMapper::getPositionForValue(double value) const
Chris@880 187 {
Chris@885 188 int pos = getPositionForValueUnclamped(value);
Chris@885 189 CoordMap::const_iterator i = m_mappings.begin();
Chris@885 190 if (pos < i->second) pos = i->second;
Chris@885 191 i = m_mappings.end(); --i;
Chris@885 192 if (pos > i->second) pos = i->second;
Chris@885 193 return pos;
Chris@885 194 }
Chris@880 195
Chris@885 196 int
Chris@1038 197 InterpolatingRangeMapper::getPositionForValueUnclamped(double value) const
Chris@885 198 {
Chris@1038 199 double p = interpolate(&m_mappings, value);
Chris@1038 200 return int(lrint(p));
Chris@880 201 }
Chris@880 202
Chris@1038 203 double
Chris@880 204 InterpolatingRangeMapper::getValueForPosition(int position) const
Chris@880 205 {
Chris@1038 206 double val = getValueForPositionUnclamped(position);
Chris@885 207 CoordMap::const_iterator i = m_mappings.begin();
Chris@885 208 if (val < i->first) val = i->first;
Chris@885 209 i = m_mappings.end(); --i;
Chris@885 210 if (val > i->first) val = i->first;
Chris@885 211 return val;
Chris@885 212 }
Chris@885 213
Chris@1038 214 double
Chris@885 215 InterpolatingRangeMapper::getValueForPositionUnclamped(int position) const
Chris@885 216 {
Chris@885 217 return interpolate(&m_reverse, position);
Chris@885 218 }
Chris@885 219
Chris@885 220 template <typename T>
Chris@1038 221 double
Chris@1038 222 InterpolatingRangeMapper::interpolate(T *mapping, double value) const
Chris@885 223 {
Chris@885 224 // lower_bound: first element which does not compare less than value
Chris@1038 225 typename T::const_iterator i =
Chris@1038 226 mapping->lower_bound(typename T::key_type(value));
Chris@885 227
Chris@885 228 if (i == mapping->begin()) {
Chris@885 229 // value is less than or equal to first element, so use the
Chris@885 230 // gradient from first to second and extend it
Chris@885 231 ++i;
Chris@885 232 }
Chris@885 233
Chris@885 234 if (i == mapping->end()) {
Chris@885 235 // value is off the end, so use the gradient from penultimate
Chris@885 236 // to ultimate and extend it
Chris@885 237 --i;
Chris@885 238 }
Chris@885 239
Chris@885 240 typename T::const_iterator j = i;
Chris@880 241 --j;
Chris@880 242
Chris@1038 243 double gradient = double(i->second - j->second) / double(i->first - j->first);
Chris@880 244
Chris@885 245 return j->second + (value - j->first) * gradient;
Chris@880 246 }
Chris@880 247
Chris@880 248 AutoRangeMapper::AutoRangeMapper(CoordMap pointMappings,
Chris@880 249 QString unit) :
Chris@880 250 m_mappings(pointMappings),
Chris@880 251 m_unit(unit)
Chris@880 252 {
Chris@880 253 m_type = chooseMappingTypeFor(m_mappings);
Chris@880 254
Chris@880 255 CoordMap::const_iterator first = m_mappings.begin();
Chris@880 256 CoordMap::const_iterator last = m_mappings.end();
Chris@880 257 --last;
Chris@880 258
Chris@880 259 switch (m_type) {
Chris@880 260 case StraightLine:
Chris@880 261 m_mapper = new LinearRangeMapper(first->second, last->second,
Chris@880 262 first->first, last->first,
Chris@880 263 unit, false);
Chris@880 264 break;
Chris@880 265 case Logarithmic:
Chris@880 266 m_mapper = new LogRangeMapper(first->second, last->second,
Chris@880 267 first->first, last->first,
Chris@880 268 unit, false);
Chris@880 269 break;
Chris@880 270 case Interpolating:
Chris@880 271 m_mapper = new InterpolatingRangeMapper(m_mappings, unit);
Chris@880 272 break;
Chris@880 273 }
Chris@880 274 }
Chris@880 275
Chris@880 276 AutoRangeMapper::~AutoRangeMapper()
Chris@880 277 {
Chris@880 278 delete m_mapper;
Chris@880 279 }
Chris@880 280
Chris@880 281 AutoRangeMapper::MappingType
Chris@880 282 AutoRangeMapper::chooseMappingTypeFor(const CoordMap &mappings)
Chris@880 283 {
Chris@880 284 // how do we work out whether a linear/log mapping is "close enough"?
Chris@880 285
Chris@880 286 CoordMap::const_iterator first = mappings.begin();
Chris@880 287 CoordMap::const_iterator last = mappings.end();
Chris@880 288 --last;
Chris@880 289
Chris@880 290 LinearRangeMapper linm(first->second, last->second,
Chris@880 291 first->first, last->first,
Chris@880 292 "", false);
Chris@880 293
Chris@880 294 bool inadequate = false;
Chris@880 295
Chris@880 296 for (CoordMap::const_iterator i = mappings.begin();
Chris@880 297 i != mappings.end(); ++i) {
Chris@880 298 int candidate = linm.getPositionForValue(i->first);
Chris@880 299 int diff = candidate - i->second;
Chris@880 300 if (diff < 0) diff = -diff;
Chris@880 301 if (diff > 1) {
Chris@885 302 // cerr << "AutoRangeMapper::chooseMappingTypeFor: diff = " << diff
Chris@885 303 // << ", straight-line mapping inadequate" << endl;
Chris@880 304 inadequate = true;
Chris@880 305 break;
Chris@880 306 }
Chris@880 307 }
Chris@880 308
Chris@880 309 if (!inadequate) {
Chris@880 310 return StraightLine;
Chris@880 311 }
Chris@880 312
Chris@880 313 LogRangeMapper logm(first->second, last->second,
Chris@880 314 first->first, last->first,
Chris@880 315 "", false);
Chris@880 316
Chris@880 317 inadequate = false;
Chris@880 318
Chris@880 319 for (CoordMap::const_iterator i = mappings.begin();
Chris@880 320 i != mappings.end(); ++i) {
Chris@880 321 int candidate = logm.getPositionForValue(i->first);
Chris@880 322 int diff = candidate - i->second;
Chris@880 323 if (diff < 0) diff = -diff;
Chris@880 324 if (diff > 1) {
Chris@885 325 // cerr << "AutoRangeMapper::chooseMappingTypeFor: diff = " << diff
Chris@885 326 // << ", log mapping inadequate" << endl;
Chris@880 327 inadequate = true;
Chris@880 328 break;
Chris@880 329 }
Chris@880 330 }
Chris@880 331
Chris@880 332 if (!inadequate) {
Chris@880 333 return Logarithmic;
Chris@880 334 }
Chris@880 335
Chris@880 336 return Interpolating;
Chris@880 337 }
Chris@880 338
Chris@880 339 int
Chris@1038 340 AutoRangeMapper::getPositionForValue(double value) const
Chris@880 341 {
Chris@880 342 return m_mapper->getPositionForValue(value);
Chris@880 343 }
Chris@880 344
Chris@1038 345 double
Chris@880 346 AutoRangeMapper::getValueForPosition(int position) const
Chris@880 347 {
Chris@880 348 return m_mapper->getValueForPosition(position);
Chris@880 349 }
Chris@885 350
Chris@885 351 int
Chris@1038 352 AutoRangeMapper::getPositionForValueUnclamped(double value) const
Chris@885 353 {
Chris@885 354 return m_mapper->getPositionForValueUnclamped(value);
Chris@885 355 }
Chris@885 356
Chris@1038 357 double
Chris@885 358 AutoRangeMapper::getValueForPositionUnclamped(int position) const
Chris@885 359 {
Chris@885 360 return m_mapper->getValueForPositionUnclamped(position);
Chris@885 361 }