annotate base/RangeMapper.cpp @ 1331:576be7933ec7 3.0-integration

Introduce a colour map combo too. Doesn't yet have swatches
author Chris Cannam
date Fri, 16 Dec 2016 14:16:05 +0000
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 }