Chris@189: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@189: Chris@189: /* Chris@189: Sonic Visualiser Chris@189: An audio file viewer and annotation editor. Chris@189: Centre for Digital Music, Queen Mary, University of London. Chris@202: This file copyright 2006 QMUL. Chris@189: Chris@189: This program is free software; you can redistribute it and/or Chris@189: modify it under the terms of the GNU General Public License as Chris@189: published by the Free Software Foundation; either version 2 of the Chris@189: License, or (at your option) any later version. See the file Chris@189: COPYING included with this distribution for more information. Chris@189: */ Chris@189: Chris@189: #include "RangeMapper.h" Chris@573: #include "system/System.h" Chris@189: Chris@189: #include Chris@189: #include Chris@189: Chris@189: #include Chris@1545: #include Chris@189: Chris@189: LinearRangeMapper::LinearRangeMapper(int minpos, int maxpos, Chris@1429: double minval, double maxval, Chris@1203: QString unit, bool inverted, Chris@1203: std::map labels) : Chris@189: m_minpos(minpos), Chris@189: m_maxpos(maxpos), Chris@189: m_minval(minval), Chris@189: m_maxval(maxval), Chris@464: m_unit(unit), Chris@1203: m_inverted(inverted), Chris@1203: m_labels(labels) Chris@189: { Chris@1545: if (m_maxval == m_minval) { Chris@1545: throw std::logic_error("LinearRangeMapper: maxval must differ from minval"); Chris@1545: } Chris@1545: if (m_maxpos == m_minpos) { Chris@1545: throw std::logic_error("LinearRangeMapper: maxpos must differ from minpos"); Chris@1545: } Chris@189: } Chris@189: Chris@189: int Chris@1038: LinearRangeMapper::getPositionForValue(double value) const Chris@189: { Chris@885: int position = getPositionForValueUnclamped(value); Chris@885: if (position < m_minpos) position = m_minpos; Chris@885: if (position > m_maxpos) position = m_maxpos; Chris@885: return position; Chris@885: } Chris@885: Chris@885: int Chris@1038: LinearRangeMapper::getPositionForValueUnclamped(double value) const Chris@885: { Chris@190: int position = m_minpos + Chris@1038: int(lrint(((value - m_minval) / (m_maxval - m_minval)) Chris@1038: * (m_maxpos - m_minpos))); Chris@879: if (m_inverted) return m_maxpos - (position - m_minpos); Chris@464: else return position; Chris@189: } Chris@189: Chris@1038: double Chris@189: LinearRangeMapper::getValueForPosition(int position) const Chris@189: { Chris@879: if (position < m_minpos) position = m_minpos; Chris@879: if (position > m_maxpos) position = m_maxpos; Chris@1038: double value = getValueForPositionUnclamped(position); Chris@885: return value; Chris@885: } Chris@885: Chris@1038: double Chris@885: LinearRangeMapper::getValueForPositionUnclamped(int position) const Chris@885: { Chris@885: if (m_inverted) position = m_maxpos - (position - m_minpos); Chris@1038: double value = m_minval + Chris@1038: ((double(position - m_minpos) / double(m_maxpos - m_minpos)) Chris@190: * (m_maxval - m_minval)); Chris@1201: // cerr << "getValueForPositionUnclamped(" << position << "): minval " << m_minval << ", maxval " << m_maxval << ", value " << value << endl; Chris@189: return value; Chris@189: } Chris@189: Chris@1203: QString Chris@1203: LinearRangeMapper::getLabel(int position) const Chris@1203: { Chris@1203: if (m_labels.find(position) != m_labels.end()) { Chris@1203: return m_labels.at(position); Chris@1203: } else { Chris@1203: return ""; Chris@1203: } Chris@1203: } Chris@1203: Chris@189: LogRangeMapper::LogRangeMapper(int minpos, int maxpos, Chris@1038: double minval, double maxval, Chris@464: QString unit, bool inverted) : Chris@189: m_minpos(minpos), Chris@189: m_maxpos(maxpos), Chris@464: m_unit(unit), Chris@464: m_inverted(inverted) Chris@189: { Chris@356: convertMinMax(minpos, maxpos, minval, maxval, m_minlog, m_ratio); Chris@356: Chris@879: // cerr << "LogRangeMapper: minpos " << minpos << ", maxpos " Chris@879: // << maxpos << ", minval " << minval << ", maxval " Chris@879: // << maxval << ", minlog " << m_minlog << ", ratio " << m_ratio Chris@879: // << ", unit " << unit << endl; Chris@356: Chris@1545: if (m_maxpos == m_minpos) { Chris@1545: throw std::logic_error("LogRangeMapper: maxpos must differ from minpos"); Chris@1545: } Chris@189: Chris@189: m_maxlog = (m_maxpos - m_minpos) / m_ratio + m_minlog; Chris@879: Chris@879: // cerr << "LogRangeMapper: maxlog = " << m_maxlog << endl; Chris@189: } Chris@189: Chris@356: void Chris@356: LogRangeMapper::convertMinMax(int minpos, int maxpos, Chris@1038: double minval, double maxval, Chris@1038: double &minlog, double &ratio) Chris@356: { Chris@1038: static double thresh = powf(10, -10); Chris@356: if (minval < thresh) minval = thresh; Chris@1038: minlog = log10(minval); Chris@1038: ratio = (maxpos - minpos) / (log10(maxval) - minlog); Chris@356: } Chris@356: Chris@356: void Chris@1038: LogRangeMapper::convertRatioMinLog(double ratio, double minlog, Chris@356: int minpos, int maxpos, Chris@1038: double &minval, double &maxval) Chris@356: { Chris@1038: minval = pow(10, minlog); Chris@1038: maxval = pow(10, (maxpos - minpos) / ratio + minlog); Chris@356: } Chris@356: Chris@189: int Chris@1038: LogRangeMapper::getPositionForValue(double value) const Chris@189: { Chris@885: int position = getPositionForValueUnclamped(value); Chris@189: if (position < m_minpos) position = m_minpos; Chris@189: if (position > m_maxpos) position = m_maxpos; Chris@885: return position; Chris@885: } Chris@885: Chris@885: int Chris@1038: LogRangeMapper::getPositionForValueUnclamped(double value) const Chris@885: { Chris@1038: static double thresh = pow(10, -10); Chris@885: if (value < thresh) value = thresh; Chris@1038: int position = int(lrint((log10(value) - m_minlog) * m_ratio)) + m_minpos; Chris@879: if (m_inverted) return m_maxpos - (position - m_minpos); Chris@464: else return position; Chris@189: } Chris@189: Chris@1038: double Chris@189: LogRangeMapper::getValueForPosition(int position) const Chris@189: { Chris@879: if (position < m_minpos) position = m_minpos; Chris@879: if (position > m_maxpos) position = m_maxpos; Chris@1038: double value = getValueForPositionUnclamped(position); Chris@885: return value; Chris@885: } Chris@885: Chris@1038: double Chris@885: LogRangeMapper::getValueForPositionUnclamped(int position) const Chris@885: { Chris@885: if (m_inverted) position = m_maxpos - (position - m_minpos); Chris@1038: double value = pow(10, (position - m_minpos) / m_ratio + m_minlog); Chris@189: return value; Chris@189: } Chris@189: Chris@880: InterpolatingRangeMapper::InterpolatingRangeMapper(CoordMap pointMappings, Chris@880: QString unit) : Chris@880: m_mappings(pointMappings), Chris@880: m_unit(unit) Chris@880: { Chris@880: for (CoordMap::const_iterator i = m_mappings.begin(); Chris@880: i != m_mappings.end(); ++i) { Chris@880: m_reverse[i->second] = i->first; Chris@880: } Chris@880: } Chris@880: Chris@880: int Chris@1038: InterpolatingRangeMapper::getPositionForValue(double value) const Chris@880: { Chris@885: int pos = getPositionForValueUnclamped(value); Chris@885: CoordMap::const_iterator i = m_mappings.begin(); Chris@885: if (pos < i->second) pos = i->second; Chris@885: i = m_mappings.end(); --i; Chris@885: if (pos > i->second) pos = i->second; Chris@885: return pos; Chris@885: } Chris@880: Chris@885: int Chris@1038: InterpolatingRangeMapper::getPositionForValueUnclamped(double value) const Chris@885: { Chris@1038: double p = interpolate(&m_mappings, value); Chris@1038: return int(lrint(p)); Chris@880: } Chris@880: Chris@1038: double Chris@880: InterpolatingRangeMapper::getValueForPosition(int position) const Chris@880: { Chris@1038: double val = getValueForPositionUnclamped(position); Chris@885: CoordMap::const_iterator i = m_mappings.begin(); Chris@885: if (val < i->first) val = i->first; Chris@885: i = m_mappings.end(); --i; Chris@885: if (val > i->first) val = i->first; Chris@885: return val; Chris@885: } Chris@885: Chris@1038: double Chris@885: InterpolatingRangeMapper::getValueForPositionUnclamped(int position) const Chris@885: { Chris@885: return interpolate(&m_reverse, position); Chris@885: } Chris@885: Chris@885: template Chris@1038: double Chris@1038: InterpolatingRangeMapper::interpolate(T *mapping, double value) const Chris@885: { Chris@885: // lower_bound: first element which does not compare less than value Chris@1038: typename T::const_iterator i = Chris@1038: mapping->lower_bound(typename T::key_type(value)); Chris@885: Chris@885: if (i == mapping->begin()) { Chris@885: // value is less than or equal to first element, so use the Chris@885: // gradient from first to second and extend it Chris@885: ++i; Chris@885: } Chris@885: Chris@885: if (i == mapping->end()) { Chris@885: // value is off the end, so use the gradient from penultimate Chris@885: // to ultimate and extend it Chris@885: --i; Chris@885: } Chris@885: Chris@885: typename T::const_iterator j = i; Chris@880: --j; Chris@880: Chris@1038: double gradient = double(i->second - j->second) / double(i->first - j->first); Chris@880: Chris@885: return j->second + (value - j->first) * gradient; Chris@880: } Chris@880: Chris@880: AutoRangeMapper::AutoRangeMapper(CoordMap pointMappings, Chris@880: QString unit) : Chris@880: m_mappings(pointMappings), Chris@880: m_unit(unit) Chris@880: { Chris@880: m_type = chooseMappingTypeFor(m_mappings); Chris@880: Chris@880: CoordMap::const_iterator first = m_mappings.begin(); Chris@880: CoordMap::const_iterator last = m_mappings.end(); Chris@880: --last; Chris@880: Chris@880: switch (m_type) { Chris@880: case StraightLine: Chris@880: m_mapper = new LinearRangeMapper(first->second, last->second, Chris@880: first->first, last->first, Chris@880: unit, false); Chris@880: break; Chris@880: case Logarithmic: Chris@880: m_mapper = new LogRangeMapper(first->second, last->second, Chris@880: first->first, last->first, Chris@880: unit, false); Chris@880: break; Chris@880: case Interpolating: Chris@880: m_mapper = new InterpolatingRangeMapper(m_mappings, unit); Chris@880: break; Chris@880: } Chris@880: } Chris@880: Chris@880: AutoRangeMapper::~AutoRangeMapper() Chris@880: { Chris@880: delete m_mapper; Chris@880: } Chris@880: Chris@880: AutoRangeMapper::MappingType Chris@880: AutoRangeMapper::chooseMappingTypeFor(const CoordMap &mappings) Chris@880: { Chris@880: // how do we work out whether a linear/log mapping is "close enough"? Chris@880: Chris@880: CoordMap::const_iterator first = mappings.begin(); Chris@880: CoordMap::const_iterator last = mappings.end(); Chris@880: --last; Chris@880: Chris@880: LinearRangeMapper linm(first->second, last->second, Chris@880: first->first, last->first, Chris@880: "", false); Chris@880: Chris@880: bool inadequate = false; Chris@880: Chris@880: for (CoordMap::const_iterator i = mappings.begin(); Chris@880: i != mappings.end(); ++i) { Chris@880: int candidate = linm.getPositionForValue(i->first); Chris@880: int diff = candidate - i->second; Chris@880: if (diff < 0) diff = -diff; Chris@880: if (diff > 1) { Chris@885: // cerr << "AutoRangeMapper::chooseMappingTypeFor: diff = " << diff Chris@885: // << ", straight-line mapping inadequate" << endl; Chris@880: inadequate = true; Chris@880: break; Chris@880: } Chris@880: } Chris@880: Chris@880: if (!inadequate) { Chris@880: return StraightLine; Chris@880: } Chris@880: Chris@880: LogRangeMapper logm(first->second, last->second, Chris@880: first->first, last->first, Chris@880: "", false); Chris@880: Chris@880: inadequate = false; Chris@880: Chris@880: for (CoordMap::const_iterator i = mappings.begin(); Chris@880: i != mappings.end(); ++i) { Chris@880: int candidate = logm.getPositionForValue(i->first); Chris@880: int diff = candidate - i->second; Chris@880: if (diff < 0) diff = -diff; Chris@880: if (diff > 1) { Chris@885: // cerr << "AutoRangeMapper::chooseMappingTypeFor: diff = " << diff Chris@885: // << ", log mapping inadequate" << endl; Chris@880: inadequate = true; Chris@880: break; Chris@880: } Chris@880: } Chris@880: Chris@880: if (!inadequate) { Chris@880: return Logarithmic; Chris@880: } Chris@880: Chris@880: return Interpolating; Chris@880: } Chris@880: Chris@880: int Chris@1038: AutoRangeMapper::getPositionForValue(double value) const Chris@880: { Chris@880: return m_mapper->getPositionForValue(value); Chris@880: } Chris@880: Chris@1038: double Chris@880: AutoRangeMapper::getValueForPosition(int position) const Chris@880: { Chris@880: return m_mapper->getValueForPosition(position); Chris@880: } Chris@885: Chris@885: int Chris@1038: AutoRangeMapper::getPositionForValueUnclamped(double value) const Chris@885: { Chris@885: return m_mapper->getPositionForValueUnclamped(value); Chris@885: } Chris@885: Chris@1038: double Chris@885: AutoRangeMapper::getValueForPositionUnclamped(int position) const Chris@885: { Chris@885: return m_mapper->getValueForPositionUnclamped(position); Chris@885: }