view base/RangeMapper.cpp @ 885:12a6140b3ae0

Add unclamped range mapper methods
author Chris Cannam
date Thu, 06 Feb 2014 15:31:16 +0000
parents b4787b595db3
children cc27f35aa75c
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */

/*
    Sonic Visualiser
    An audio file viewer and annotation editor.
    Centre for Digital Music, Queen Mary, University of London.
    This file copyright 2006 QMUL.
    
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of the
    License, or (at your option) any later version.  See the file
    COPYING included with this distribution for more information.
*/

#include "RangeMapper.h"
#include "system/System.h"

#include <cassert>
#include <cmath>

#include <iostream>

LinearRangeMapper::LinearRangeMapper(int minpos, int maxpos,
				     float minval, float maxval,
                                     QString unit, bool inverted) :
    m_minpos(minpos),
    m_maxpos(maxpos),
    m_minval(minval),
    m_maxval(maxval),
    m_unit(unit),
    m_inverted(inverted)
{
    assert(m_maxval != m_minval);
    assert(m_maxpos != m_minpos);
}

int
LinearRangeMapper::getPositionForValue(float value) const
{
    int position = getPositionForValueUnclamped(value);
    if (position < m_minpos) position = m_minpos;
    if (position > m_maxpos) position = m_maxpos;
    return position;
}

int
LinearRangeMapper::getPositionForValueUnclamped(float value) const
{
    int position = m_minpos +
        lrintf(((value - m_minval) / (m_maxval - m_minval))
               * (m_maxpos - m_minpos));
    if (m_inverted) return m_maxpos - (position - m_minpos);
    else return position;
}

float
LinearRangeMapper::getValueForPosition(int position) const
{
    if (position < m_minpos) position = m_minpos;
    if (position > m_maxpos) position = m_maxpos;
    float value = getValueForPositionUnclamped(position);
    return value;
}

float
LinearRangeMapper::getValueForPositionUnclamped(int position) const
{
    if (m_inverted) position = m_maxpos - (position - m_minpos);
    float value = m_minval +
        ((float(position - m_minpos) / float(m_maxpos - m_minpos))
         * (m_maxval - m_minval));
    return value;
}

LogRangeMapper::LogRangeMapper(int minpos, int maxpos,
                               float minval, float maxval,
                               QString unit, bool inverted) :
    m_minpos(minpos),
    m_maxpos(maxpos),
    m_unit(unit),
    m_inverted(inverted)
{
    convertMinMax(minpos, maxpos, minval, maxval, m_minlog, m_ratio);

//    cerr << "LogRangeMapper: minpos " << minpos << ", maxpos "
//              << maxpos << ", minval " << minval << ", maxval "
//              << maxval << ", minlog " << m_minlog << ", ratio " << m_ratio
//              << ", unit " << unit << endl;

    assert(m_maxpos != m_minpos);

    m_maxlog = (m_maxpos - m_minpos) / m_ratio + m_minlog;

//    cerr << "LogRangeMapper: maxlog = " << m_maxlog << endl;
}

void
LogRangeMapper::convertMinMax(int minpos, int maxpos,
                              float minval, float maxval,
                              float &minlog, float &ratio)
{
    static float thresh = powf(10, -10);
    if (minval < thresh) minval = thresh;
    minlog = log10f(minval);
    ratio = (maxpos - minpos) / (log10f(maxval) - minlog);
}

void
LogRangeMapper::convertRatioMinLog(float ratio, float minlog,
                                   int minpos, int maxpos,
                                   float &minval, float &maxval)
{
    minval = powf(10, minlog);
    maxval = powf(10, (maxpos - minpos) / ratio + minlog);
}

int
LogRangeMapper::getPositionForValue(float value) const
{
    int position = getPositionForValueUnclamped(value);
    if (position < m_minpos) position = m_minpos;
    if (position > m_maxpos) position = m_maxpos;
    return position;
}

int
LogRangeMapper::getPositionForValueUnclamped(float value) const
{
    static float thresh = powf(10, -10);
    if (value < thresh) value = thresh;
    int position = lrintf((log10(value) - m_minlog) * m_ratio) + m_minpos;
    if (m_inverted) return m_maxpos - (position - m_minpos);
    else return position;
}

float
LogRangeMapper::getValueForPosition(int position) const
{
    if (position < m_minpos) position = m_minpos;
    if (position > m_maxpos) position = m_maxpos;
    float value = getValueForPositionUnclamped(position);
    return value;
}

float
LogRangeMapper::getValueForPositionUnclamped(int position) const
{
    if (m_inverted) position = m_maxpos - (position - m_minpos);
    float value = powf(10, (position - m_minpos) / m_ratio + m_minlog);
    return value;
}

InterpolatingRangeMapper::InterpolatingRangeMapper(CoordMap pointMappings,
                                                   QString unit) :
    m_mappings(pointMappings),
    m_unit(unit)
{
    for (CoordMap::const_iterator i = m_mappings.begin(); 
         i != m_mappings.end(); ++i) {
        m_reverse[i->second] = i->first;
    }
}

int
InterpolatingRangeMapper::getPositionForValue(float value) const
{
    int pos = getPositionForValueUnclamped(value);
    CoordMap::const_iterator i = m_mappings.begin();
    if (pos < i->second) pos = i->second;
    i = m_mappings.end(); --i;
    if (pos > i->second) pos = i->second;
    return pos;
}

int
InterpolatingRangeMapper::getPositionForValueUnclamped(float value) const
{
    float p = interpolate(&m_mappings, value);
    return lrintf(p);
}

float
InterpolatingRangeMapper::getValueForPosition(int position) const
{
    float val = getValueForPositionUnclamped(position);
    CoordMap::const_iterator i = m_mappings.begin();
    if (val < i->first) val = i->first;
    i = m_mappings.end(); --i;
    if (val > i->first) val = i->first;
    return val;
}

float
InterpolatingRangeMapper::getValueForPositionUnclamped(int position) const
{
    return interpolate(&m_reverse, position);
}

template <typename T>
float
InterpolatingRangeMapper::interpolate(T *mapping, float value) const
{
    // lower_bound: first element which does not compare less than value
    typename T::const_iterator i = mapping->lower_bound(value);

    if (i == mapping->begin()) {
        // value is less than or equal to first element, so use the
        // gradient from first to second and extend it
        ++i;
    }

    if (i == mapping->end()) {
        // value is off the end, so use the gradient from penultimate
        // to ultimate and extend it
        --i;
    }

    typename T::const_iterator j = i;
    --j;

    float gradient = float(i->second - j->second) / float(i->first - j->first);

    return j->second + (value - j->first) * gradient;
}

AutoRangeMapper::AutoRangeMapper(CoordMap pointMappings,
                                 QString unit) :
    m_mappings(pointMappings),
    m_unit(unit)
{
    m_type = chooseMappingTypeFor(m_mappings);

    CoordMap::const_iterator first = m_mappings.begin();
    CoordMap::const_iterator last = m_mappings.end();
    --last;

    switch (m_type) {
    case StraightLine:
        m_mapper = new LinearRangeMapper(first->second, last->second,
                                         first->first, last->first,
                                         unit, false);
        break;
    case Logarithmic:
        m_mapper = new LogRangeMapper(first->second, last->second,
                                      first->first, last->first,
                                      unit, false);
        break;
    case Interpolating:
        m_mapper = new InterpolatingRangeMapper(m_mappings, unit);
        break;
    }
}

AutoRangeMapper::~AutoRangeMapper()
{
    delete m_mapper;
}

AutoRangeMapper::MappingType
AutoRangeMapper::chooseMappingTypeFor(const CoordMap &mappings)
{
    // how do we work out whether a linear/log mapping is "close enough"?

    CoordMap::const_iterator first = mappings.begin();
    CoordMap::const_iterator last = mappings.end();
    --last;

    LinearRangeMapper linm(first->second, last->second,
                           first->first, last->first,
                           "", false);

    bool inadequate = false;

    for (CoordMap::const_iterator i = mappings.begin();
         i != mappings.end(); ++i) {
        int candidate = linm.getPositionForValue(i->first);
        int diff = candidate - i->second;
        if (diff < 0) diff = -diff;
        if (diff > 1) {
//            cerr << "AutoRangeMapper::chooseMappingTypeFor: diff = " << diff
//                 << ", straight-line mapping inadequate" << endl;
            inadequate = true;
            break;
        }
    }

    if (!inadequate) {
        return StraightLine;
    }

    LogRangeMapper logm(first->second, last->second,
                        first->first, last->first,
                        "", false);

    inadequate = false;

    for (CoordMap::const_iterator i = mappings.begin();
         i != mappings.end(); ++i) {
        int candidate = logm.getPositionForValue(i->first);
        int diff = candidate - i->second;
        if (diff < 0) diff = -diff;
        if (diff > 1) {
//            cerr << "AutoRangeMapper::chooseMappingTypeFor: diff = " << diff
//                 << ", log mapping inadequate" << endl;
            inadequate = true;
            break;
        }
    }

    if (!inadequate) {
        return Logarithmic;
    }

    return Interpolating;
}

int
AutoRangeMapper::getPositionForValue(float value) const
{
    return m_mapper->getPositionForValue(value);
}

float
AutoRangeMapper::getValueForPosition(int position) const
{
    return m_mapper->getValueForPosition(position);
}

int
AutoRangeMapper::getPositionForValueUnclamped(float value) const
{
    return m_mapper->getPositionForValueUnclamped(value);
}

float
AutoRangeMapper::getValueForPositionUnclamped(int position) const
{
    return m_mapper->getValueForPositionUnclamped(position);
}