annotate base/RangeMapper.cpp @ 1777:d484490cdf69

Split EditableDenseThreeDimensionalModel into explicitly compressed and uncompressed variants. Simplifies the uncompressed version, and we may want to consider whether we need the compressed one at all.
author Chris Cannam
date Tue, 10 Sep 2019 16:34:47 +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 }