view base/RangeMapper.cpp @ 884:633a8fa622c9

RangeMapperTest -> TestRangeMapper
author Chris Cannam
date Thu, 06 Feb 2014 10:14:59 +0000
parents b4787b595db3
children 12a6140b3ae0
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 = m_minpos +
        lrintf(((value - m_minval) / (m_maxval - m_minval))
               * (m_maxpos - m_minpos));
    if (position < m_minpos) position = m_minpos;
    if (position > m_maxpos) position = m_maxpos;
    if (m_inverted) return m_maxpos - (position - m_minpos);
    else return position;
}

float
LinearRangeMapper::getValueForPosition(int position) const
{
    if (m_inverted) position = m_maxpos - (position - m_minpos);
    if (position < m_minpos) position = m_minpos;
    if (position > m_maxpos) position = m_maxpos;
    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 = lrintf((log10(value) - m_minlog) * m_ratio) + m_minpos;
    if (position < m_minpos) position = m_minpos;
    if (position > m_maxpos) position = m_maxpos;
//    cerr << "LogRangeMapper::getPositionForValue: " << value << " -> "
//              << position << " (minpos " << m_minpos << ", maxpos " << m_maxpos << ", ratio " << m_ratio << ", minlog " << m_minlog << ")" << endl;
    if (m_inverted) return m_maxpos - (position - m_minpos);
    else return position;
}

float
LogRangeMapper::getValueForPosition(int position) const
{
    if (m_inverted) position = m_maxpos - (position - m_minpos);
    if (position < m_minpos) position = m_minpos;
    if (position > m_maxpos) position = m_maxpos;
    float value = powf(10, (position - m_minpos) / m_ratio + m_minlog);
//    cerr << "LogRangeMapper::getValueForPosition: " << position << " -> "
//              << value << " (minpos " << m_minpos << ", maxpos " << m_maxpos << ", ratio " << m_ratio << ", minlog " << m_minlog << ")" << endl;
    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
{
    CoordMap::const_iterator i = m_mappings.lower_bound(value);
    CoordMap::const_iterator j = i;
    --j;

    if (i == m_mappings.end()) return j->second;
    if (i == m_mappings.begin()) return i->second;
    if (i->first == value) return i->second;

    return lrintf(((value - j->first) / (i->first - j->first)) *
                  float(i->second - j->second) + j->second);
}

float
InterpolatingRangeMapper::getValueForPosition(int position) const
{
    std::map<int, float>::const_iterator i = m_reverse.lower_bound(position);
    std::map<int, float>::const_iterator j = i;
    --j;

    if (i == m_reverse.end()) return j->second;
    if (i == m_reverse.begin()) return i->second;
    if (i->first == position) return i->second;

    return ((float(position) - j->first) / (i->first - j->first)) *
        (i->second - j->second) + j->second;
}

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);
}