# HG changeset patch # User Chris Cannam # Date 1394559151 0 # Node ID 8962f80f5d8e7f28fb9268c02409416d7828cca2 # Parent 4cbf8c6a462dd25ef0e6b12f7d22dbf3fcbec1e9# Parent 7f97a4d9d14f09517a4e25f172305d5a9c64b36a Merge from default branch diff -r 4cbf8c6a462d -r 8962f80f5d8e base/RangeMapper.cpp --- a/base/RangeMapper.cpp Tue Mar 11 17:30:35 2014 +0000 +++ b/base/RangeMapper.cpp Tue Mar 11 17:32:31 2014 +0000 @@ -38,28 +38,38 @@ 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 (position < m_minpos) position = m_minpos; - if (position > m_maxpos) position = m_maxpos; -// SVDEBUG << "LinearRangeMapper::getPositionForValue: " << value << " -> " -// << position << " (minpos " << m_minpos << ", maxpos " << m_maxpos << ", minval " << m_minval << ", maxval " << m_maxval << ")" << endl; - if (m_inverted) return m_maxpos - position; + 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; + 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)); - if (value < m_minval) value = m_minval; - if (value > m_maxval) value = m_maxval; -// SVDEBUG << "LinearRangeMapper::getValueForPosition: " << position << " -> " -// << value << " (minpos " << m_minpos << ", maxpos " << m_maxpos << ", minval " << m_minval << ", maxval " << m_maxval << ")" << endl; return value; } @@ -73,14 +83,16 @@ { 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; +// 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 @@ -106,22 +118,223 @@ int LogRangeMapper::getPositionForValue(float value) const { - int position = (log10(value) - m_minlog) * m_ratio + m_minpos; + int position = getPositionForValueUnclamped(value); if (position < m_minpos) position = m_minpos; if (position > m_maxpos) position = m_maxpos; -// SVDEBUG << "LogRangeMapper::getPositionForValue: " << value << " -> " -// << position << " (minpos " << m_minpos << ", maxpos " << m_maxpos << ", ratio " << m_ratio << ", minlog " << m_minlog << ")" << endl; - if (m_inverted) return m_maxpos - position; + 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 (m_inverted) position = m_maxpos - position; - float value = powf(10, (position - m_minpos) / m_ratio + m_minlog); -// SVDEBUG << "LogRangeMapper::getValueForPosition: " << position << " -> " -// << value << " (minpos " << m_minpos << ", maxpos " << m_maxpos << ", ratio " << m_ratio << ", minlog " << m_minlog << ")" << endl; + 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 +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); +} diff -r 4cbf8c6a462d -r 8962f80f5d8e base/RangeMapper.h --- a/base/RangeMapper.h Tue Mar 11 17:30:35 2014 +0000 +++ b/base/RangeMapper.h Tue Mar 11 17:32:31 2014 +0000 @@ -19,14 +19,48 @@ #include #include "Debug.h" - +#include class RangeMapper { public: virtual ~RangeMapper() { } + + /** + * Return the position that maps to the given value, rounding to + * the nearest position and clamping to the minimum and maximum + * extents of the mapper's positional range. + */ virtual int getPositionForValue(float value) const = 0; + + /** + * Return the position that maps to the given value, rounding to + * the nearest position, without clamping. That is, whatever + * mapping function is in use will be projected even outside the + * minimum and maximum extents of the mapper's positional + * range. (The mapping outside that range is not guaranteed to be + * exact, except if the mapper is a linear one.) + */ + virtual int getPositionForValueUnclamped(float value) const = 0; + + /** + * Return the value mapped from the given position, clamping to + * the minimum and maximum extents of the mapper's value range. + */ virtual float getValueForPosition(int position) const = 0; + + /** + * Return the value mapped from the given positionq, without + * clamping. That is, whatever mapping function is in use will be + * projected even outside the minimum and maximum extents of the + * mapper's value range. (The mapping outside that range is not + * guaranteed to be exact, except if the mapper is a linear one.) + */ + virtual float getValueForPositionUnclamped(int position) const = 0; + + /** + * Get the unit of the mapper's value range. + */ virtual QString getUnit() const { return ""; } }; @@ -34,12 +68,21 @@ class LinearRangeMapper : public RangeMapper { public: + /** + * Map values in range minval->maxval linearly into integer range + * minpos->maxpos. minval and minpos must be less than maxval and + * maxpos respectively. If inverted is true, the range will be + * mapped "backwards" (minval to maxpos and maxval to minpos). + */ LinearRangeMapper(int minpos, int maxpos, float minval, float maxval, QString unit = "", bool inverted = false); virtual int getPositionForValue(float value) const; + virtual int getPositionForValueUnclamped(float value) const; + virtual float getValueForPosition(int position) const; + virtual float getValueForPositionUnclamped(int position) const; virtual QString getUnit() const { return m_unit; } @@ -52,10 +95,17 @@ bool m_inverted; }; - class LogRangeMapper : public RangeMapper { public: + /** + * Map values in range minval->maxval into integer range + * minpos->maxpos such that logs of the values are mapped + * linearly. minval must be greater than zero, and minval and + * minpos must be less than maxval and maxpos respectively. If + * inverted is true, the range will be mapped "backwards" (minval + * to maxpos and maxval to minpos). + */ LogRangeMapper(int minpos, int maxpos, float minval, float maxval, QString m_unit = "", bool inverted = false); @@ -69,7 +119,10 @@ float &ratio, float &minlog); virtual int getPositionForValue(float value) const; + virtual int getPositionForValueUnclamped(float value) const; + virtual float getValueForPosition(int position) const; + virtual float getValueForPositionUnclamped(int position) const; virtual QString getUnit() const { return m_unit; } @@ -83,5 +136,120 @@ bool m_inverted; }; +class InterpolatingRangeMapper : public RangeMapper +{ +public: + typedef std::map CoordMap; + + /** + * Given a series of (value, position) coordinate mappings, + * construct a range mapper that maps arbitrary values, in the + * range between minimum and maximum of the provided values, onto + * coordinates using linear interpolation between the supplied + * points. + * + *!!! todo: Cubic -- more generally useful than linear interpolation + *!!! todo: inverted flag + * + * The set of provided mappings must contain at least two + * coordinates. + * + * It is expected that the values and positions in the coordinate + * mappings will both be monotonically increasing (i.e. no + * inflections in the mapping curve). Behaviour is undefined if + * this is not the case. + */ + InterpolatingRangeMapper(CoordMap pointMappings, + QString unit); + + virtual int getPositionForValue(float value) const; + virtual int getPositionForValueUnclamped(float value) const; + + virtual float getValueForPosition(int position) const; + virtual float getValueForPositionUnclamped(int position) const; + + virtual QString getUnit() const { return m_unit; } + +protected: + CoordMap m_mappings; + std::map m_reverse; + QString m_unit; + + template + float interpolate(T *mapping, float v) const; +}; + +class AutoRangeMapper : public RangeMapper +{ +public: + enum MappingType { + Interpolating, + StraightLine, + Logarithmic, + }; + + typedef std::map CoordMap; + + /** + * Given a series of (value, position) coordinate mappings, + * construct a range mapper that maps arbitrary values, in the + * range between minimum and maximum of the provided values, onto + * coordinates. + * + * The mapping used may be + * + * Interpolating -- an InterpolatingRangeMapper will be used + * + * StraightLine -- a LinearRangeMapper from the minimum to + * maximum value coordinates will be used, ignoring all other + * supplied coordinate mappings + * + * Logarithmic -- a LogRangeMapper from the minimum to + * maximum value coordinates will be used, ignoring all other + * supplied coordinate mappings + * + * The mapping will be chosen automatically by looking at the + * supplied coordinates. If the supplied coordinates fall on a + * straight line, a StraightLine mapping will be used; if they + * fall on a log curve, a Logarithmic mapping will be used; + * otherwise an Interpolating mapping will be used. + * + *!!! todo: inverted flag + * + * The set of provided mappings must contain at least two + * coordinates, or at least three if the points are not supposed + * to be in a straight line. + * + * It is expected that the values and positions in the coordinate + * mappings will both be monotonically increasing (i.e. no + * inflections in the mapping curve). Behaviour is undefined if + * this is not the case. + */ + AutoRangeMapper(CoordMap pointMappings, + QString unit); + + ~AutoRangeMapper(); + + /** + * Return the mapping type in use. + */ + MappingType getType() const { return m_type; } + + virtual int getPositionForValue(float value) const; + virtual int getPositionForValueUnclamped(float value) const; + + virtual float getValueForPosition(int position) const; + virtual float getValueForPositionUnclamped(int position) const; + + virtual QString getUnit() const { return m_unit; } + +protected: + MappingType m_type; + CoordMap m_mappings; + QString m_unit; + RangeMapper *m_mapper; + + MappingType chooseMappingTypeFor(const CoordMap &); +}; #endif diff -r 4cbf8c6a462d -r 8962f80f5d8e base/test/TestRangeMapper.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/test/TestRangeMapper.h Tue Mar 11 17:32:31 2014 +0000 @@ -0,0 +1,284 @@ +/* -*- 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 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. +*/ + +#ifndef TEST_RANGE_MAPPER_H +#define TEST_RANGE_MAPPER_H + +#include "../RangeMapper.h" + +#include +#include +#include + +#include + +using namespace std; + +class TestRangeMapper : public QObject +{ + Q_OBJECT + +private slots: + void linearUpForward() + { + LinearRangeMapper rm(1, 8, 0.5, 4.0, "x", false); + QCOMPARE(rm.getUnit(), QString("x")); + QCOMPARE(rm.getPositionForValue(0.5), 1); + QCOMPARE(rm.getPositionForValue(4.0), 8); + QCOMPARE(rm.getPositionForValue(3.0), 6); + QCOMPARE(rm.getPositionForValue(3.1), 6); + QCOMPARE(rm.getPositionForValue(3.4), 7); + QCOMPARE(rm.getPositionForValue(0.2), 1); + QCOMPARE(rm.getPositionForValue(-12), 1); + QCOMPARE(rm.getPositionForValue(6.1), 8); + QCOMPARE(rm.getPositionForValueUnclamped(3.0), 6); + QCOMPARE(rm.getPositionForValueUnclamped(0.2), 0); + QCOMPARE(rm.getPositionForValueUnclamped(-12), -24); + QCOMPARE(rm.getPositionForValueUnclamped(6.1), 12); + } + + void linearDownForward() + { + LinearRangeMapper rm(1, 8, 0.5, 4.0, "x", true); + QCOMPARE(rm.getUnit(), QString("x")); + QCOMPARE(rm.getPositionForValue(0.5), 8); + QCOMPARE(rm.getPositionForValue(4.0), 1); + QCOMPARE(rm.getPositionForValue(3.0), 3); + QCOMPARE(rm.getPositionForValue(3.1), 3); + QCOMPARE(rm.getPositionForValue(3.4), 2); + QCOMPARE(rm.getPositionForValue(0.2), 8); + QCOMPARE(rm.getPositionForValue(-12), 8); + QCOMPARE(rm.getPositionForValue(6.1), 1); + QCOMPARE(rm.getPositionForValueUnclamped(3.0), 3); + QCOMPARE(rm.getPositionForValueUnclamped(0.2), 9); + QCOMPARE(rm.getPositionForValueUnclamped(-12), 33); + QCOMPARE(rm.getPositionForValueUnclamped(6.1), -3); + } + + void linearUpBackward() + { + LinearRangeMapper rm(1, 8, 0.5, 4.0, "x", false); + QCOMPARE(rm.getUnit(), QString("x")); + QCOMPARE(rm.getValueForPosition(1), 0.5); + QCOMPARE(rm.getValueForPosition(8), 4.0); + QCOMPARE(rm.getValueForPosition(6), 3.0); + QCOMPARE(rm.getValueForPosition(7), 3.5); + QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(1)); + QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(8)); + QCOMPARE(rm.getValueForPositionUnclamped(6), 3.0); + QCOMPARE(rm.getValueForPositionUnclamped(0), 0.0); + QCOMPARE(rm.getValueForPositionUnclamped(-24), -12.0); + QCOMPARE(rm.getValueForPositionUnclamped(12), 6.0); + } + + void linearDownBackward() + { + LinearRangeMapper rm(1, 8, 0.5, 4.0, "x", true); + QCOMPARE(rm.getUnit(), QString("x")); + QCOMPARE(rm.getValueForPosition(8), 0.5); + QCOMPARE(rm.getValueForPosition(1), 4.0); + QCOMPARE(rm.getValueForPosition(3), 3.0); + QCOMPARE(rm.getValueForPosition(2), 3.5); + QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(1)); + QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(8)); + QCOMPARE(rm.getValueForPositionUnclamped(3), 3.0); + QCOMPARE(rm.getValueForPositionUnclamped(9), 0.0); + QCOMPARE(rm.getValueForPositionUnclamped(33), -12.0); + QCOMPARE(rm.getValueForPositionUnclamped(-3), 6.0); + } + + void logUpForward() + { + LogRangeMapper rm(3, 7, 10, 100000, "x", false); + QCOMPARE(rm.getUnit(), QString("x")); + QCOMPARE(rm.getPositionForValue(10.0), 3); + QCOMPARE(rm.getPositionForValue(100000.0), 7); + QCOMPARE(rm.getPositionForValue(1.0), 3); + QCOMPARE(rm.getPositionForValue(1000000.0), 7); + QCOMPARE(rm.getPositionForValue(1000.0), 5); + QCOMPARE(rm.getPositionForValue(900.0), 5); + QCOMPARE(rm.getPositionForValue(20000), 6); + QCOMPARE(rm.getPositionForValueUnclamped(1.0), 2); + QCOMPARE(rm.getPositionForValueUnclamped(1000000.0), 8); + QCOMPARE(rm.getPositionForValueUnclamped(1000.0), 5); + } + + void logDownForward() + { + LogRangeMapper rm(3, 7, 10, 100000, "x", true); + QCOMPARE(rm.getUnit(), QString("x")); + QCOMPARE(rm.getPositionForValue(10.0), 7); + QCOMPARE(rm.getPositionForValue(100000.0), 3); + QCOMPARE(rm.getPositionForValue(1.0), 7); + QCOMPARE(rm.getPositionForValue(1000000.0), 3); + QCOMPARE(rm.getPositionForValue(1000.0), 5); + QCOMPARE(rm.getPositionForValue(900.0), 5); + QCOMPARE(rm.getPositionForValue(20000), 4); + QCOMPARE(rm.getPositionForValueUnclamped(1.0), 8); + QCOMPARE(rm.getPositionForValueUnclamped(1000000.0), 2); + QCOMPARE(rm.getPositionForValueUnclamped(1000.0), 5); + } + + void logUpBackward() + { + LogRangeMapper rm(3, 7, 10, 100000, "x", false); + QCOMPARE(rm.getUnit(), QString("x")); + QCOMPARE(rm.getValueForPosition(3), 10.0); + QCOMPARE(rm.getValueForPosition(7), 100000.0); + QCOMPARE(rm.getValueForPosition(5), 1000.0); + QCOMPARE(rm.getValueForPosition(6), 10000.0); + QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(3)); + QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(7)); + QCOMPARE(rm.getValueForPositionUnclamped(2), 1.0); + QCOMPARE(rm.getValueForPositionUnclamped(8), 1000000.0); + QCOMPARE(rm.getValueForPositionUnclamped(5), 1000.0); + } + + void logDownBackward() + { + LogRangeMapper rm(3, 7, 10, 100000, "x", true); + QCOMPARE(rm.getUnit(), QString("x")); + QCOMPARE(rm.getValueForPosition(7), 10.0); + QCOMPARE(rm.getValueForPosition(3), 100000.0); + QCOMPARE(rm.getValueForPosition(5), 1000.0); + QCOMPARE(rm.getValueForPosition(4), 10000.0); + QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(3)); + QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(7)); + QCOMPARE(rm.getValueForPositionUnclamped(8), 1.0); + QCOMPARE(rm.getValueForPositionUnclamped(2), 1000000.0); + QCOMPARE(rm.getValueForPositionUnclamped(5), 1000.0); + } + + void interpolatingForward() + { + InterpolatingRangeMapper::CoordMap mappings; + mappings[1] = 10; + mappings[3] = 30; + mappings[5] = 70; + InterpolatingRangeMapper rm(mappings, "x"); + QCOMPARE(rm.getUnit(), QString("x")); + QCOMPARE(rm.getPositionForValue(1.0), 10); + QCOMPARE(rm.getPositionForValue(0.0), 10); + QCOMPARE(rm.getPositionForValue(5.0), 70); + QCOMPARE(rm.getPositionForValue(6.0), 70); + QCOMPARE(rm.getPositionForValue(3.0), 30); + QCOMPARE(rm.getPositionForValue(2.5), 25); + QCOMPARE(rm.getPositionForValue(4.5), 60); + QCOMPARE(rm.getPositionForValueUnclamped(0.0), 0); + QCOMPARE(rm.getPositionForValueUnclamped(2.5), 25); + QCOMPARE(rm.getPositionForValueUnclamped(6.0), 90); + } + + void interpolatingBackward() + { + InterpolatingRangeMapper::CoordMap mappings; + mappings[1] = 10; + mappings[3] = 30; + mappings[5] = 70; + InterpolatingRangeMapper rm(mappings, "x"); + QCOMPARE(rm.getUnit(), QString("x")); + QCOMPARE(rm.getValueForPosition(10), 1.0); + QCOMPARE(rm.getValueForPosition(9), 1.0); + QCOMPARE(rm.getValueForPosition(70), 5.0); + QCOMPARE(rm.getValueForPosition(80), 5.0); + QCOMPARE(rm.getValueForPosition(30), 3.0); + QCOMPARE(rm.getValueForPosition(25), 2.5); + QCOMPARE(rm.getValueForPosition(60), 4.5); + } + + void autoLinearForward() + { + AutoRangeMapper::CoordMap mappings; + mappings[0.5] = 1; + mappings[4.0] = 8; + AutoRangeMapper rm1(mappings, "x"); + QCOMPARE(rm1.getUnit(), QString("x")); + QCOMPARE(rm1.getType(), AutoRangeMapper::StraightLine); + QCOMPARE(rm1.getPositionForValue(0.1), 1); + QCOMPARE(rm1.getPositionForValue(0.5), 1); + QCOMPARE(rm1.getPositionForValue(4.0), 8); + QCOMPARE(rm1.getPositionForValue(4.5), 8); + QCOMPARE(rm1.getPositionForValue(3.0), 6); + QCOMPARE(rm1.getPositionForValue(3.1), 6); + QCOMPARE(rm1.getPositionForValueUnclamped(0.1), 0); + QCOMPARE(rm1.getPositionForValueUnclamped(3.1), 6); + QCOMPARE(rm1.getPositionForValueUnclamped(4.5), 9); + mappings[3.0] = 6; + mappings[3.5] = 7; + AutoRangeMapper rm2(mappings, "x"); + QCOMPARE(rm2.getUnit(), QString("x")); + QCOMPARE(rm2.getType(), AutoRangeMapper::StraightLine); + QCOMPARE(rm2.getPositionForValue(0.5), 1); + QCOMPARE(rm2.getPositionForValue(4.0), 8); + QCOMPARE(rm2.getPositionForValue(3.0), 6); + QCOMPARE(rm2.getPositionForValue(3.1), 6); + } + + void autoLogForward() + { + AutoRangeMapper::CoordMap mappings; + mappings[10] = 3; + mappings[1000] = 5; + mappings[100000] = 7; + AutoRangeMapper rm1(mappings, "x"); + QCOMPARE(rm1.getUnit(), QString("x")); + QCOMPARE(rm1.getType(), AutoRangeMapper::Logarithmic); + QCOMPARE(rm1.getPositionForValue(10.0), 3); + QCOMPARE(rm1.getPositionForValue(100000.0), 7); + QCOMPARE(rm1.getPositionForValue(1.0), 3); + QCOMPARE(rm1.getPositionForValue(1000000.0), 7); + QCOMPARE(rm1.getPositionForValue(1000.0), 5); + QCOMPARE(rm1.getPositionForValue(900.0), 5); + QCOMPARE(rm1.getPositionForValue(20000), 6); + QCOMPARE(rm1.getPositionForValueUnclamped(1.0), 2); + QCOMPARE(rm1.getPositionForValueUnclamped(900.0), 5); + QCOMPARE(rm1.getPositionForValueUnclamped(1000000.0), 8); + mappings[100] = 4; + AutoRangeMapper rm2(mappings, "x"); + QCOMPARE(rm2.getUnit(), QString("x")); + QCOMPARE(rm2.getType(), AutoRangeMapper::Logarithmic); + QCOMPARE(rm2.getPositionForValue(10.0), 3); + QCOMPARE(rm2.getPositionForValue(100000.0), 7); + QCOMPARE(rm2.getPositionForValue(1.0), 3); + QCOMPARE(rm2.getPositionForValue(1000000.0), 7); + QCOMPARE(rm2.getPositionForValue(1000.0), 5); + QCOMPARE(rm2.getPositionForValue(900.0), 5); + QCOMPARE(rm2.getPositionForValue(20000), 6); + } + + void autoInterpolatingForward() + { + AutoRangeMapper::CoordMap mappings; + mappings[1] = 10; + mappings[3] = 30; + mappings[5] = 70; + AutoRangeMapper rm(mappings, "x"); + QCOMPARE(rm.getUnit(), QString("x")); + QCOMPARE(rm.getType(), AutoRangeMapper::Interpolating); + QCOMPARE(rm.getPositionForValue(1.0), 10); + QCOMPARE(rm.getPositionForValue(0.0), 10); + QCOMPARE(rm.getPositionForValue(5.0), 70); + QCOMPARE(rm.getPositionForValue(6.0), 70); + QCOMPARE(rm.getPositionForValue(3.0), 30); + QCOMPARE(rm.getPositionForValue(2.5), 25); + QCOMPARE(rm.getPositionForValue(4.5), 60); + QCOMPARE(rm.getPositionForValueUnclamped(0.0), 0); + QCOMPARE(rm.getPositionForValueUnclamped(5.0), 70); + QCOMPARE(rm.getPositionForValueUnclamped(6.0), 90); + } +}; + +#endif + + diff -r 4cbf8c6a462d -r 8962f80f5d8e base/test/main.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/test/main.cpp Tue Mar 11 17:32:31 2014 +0000 @@ -0,0 +1,41 @@ +/* -*- 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 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 "TestRangeMapper.h" + +#include + +#include + +int main(int argc, char *argv[]) +{ + int good = 0, bad = 0; + + QCoreApplication app(argc, argv); + app.setOrganizationName("Sonic Visualiser"); + app.setApplicationName("test-svcore-base"); + + { + TestRangeMapper t; + if (QTest::qExec(&t, argc, argv) == 0) ++good; + else ++bad; + } + + if (bad > 0) { + cerr << "\n********* " << bad << " test suite(s) failed!\n" << endl; + return 1; + } else { + cerr << "All tests passed" << endl; + return 0; + } +} diff -r 4cbf8c6a462d -r 8962f80f5d8e base/test/test.pro --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/test/test.pro Tue Mar 11 17:32:31 2014 +0000 @@ -0,0 +1,53 @@ + +TEMPLATE = app + +LIBS += -L../.. -L../../../dataquay -L../../release -L../../../dataquay/release -lsvcore -ldataquay + +win32-g++ { + INCLUDEPATH += ../../../sv-dependency-builds/win32-mingw/include + LIBS += -L../../../sv-dependency-builds/win32-mingw/lib +} + +exists(../../config.pri) { + include(../../config.pri) +} + +win* { + !exists(../../config.pri) { + DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_VAMP HAVE_VAMPHOSTSDK HAVE_RUBBERBAND HAVE_DATAQUAY HAVE_LIBLO HAVE_MAD HAVE_ID3TAG HAVE_PORTAUDIO_2_0 + LIBS += -lbz2 -lrubberband -lvamp-hostsdk -lfftw3 -lfftw3f -lsndfile -lFLAC -logg -lvorbis -lvorbisenc -lvorbisfile -logg -lmad -lid3tag -lportaudio -lsamplerate -llo -lz -lsord-0 -lserd-0 -lwinmm -lws2_32 + } +} + +CONFIG += qt thread warn_on stl rtti exceptions console +QT += network xml testlib +QT -= gui + +TARGET = svcore-base-test + +DEPENDPATH += ../.. +INCLUDEPATH += ../.. +OBJECTS_DIR = o +MOC_DIR = o + +HEADERS += TestRangeMapper.h +SOURCES += main.cpp + +win* { +//PRE_TARGETDEPS += ../../svcore.lib +} +!win* { +PRE_TARGETDEPS += ../../libsvcore.a +} + +!win32 { + !macx* { + QMAKE_POST_LINK=./$${TARGET} + } + macx* { + QMAKE_POST_LINK=./$${TARGET}.app/Contents/MacOS/$${TARGET} + } +} + +win32:QMAKE_POST_LINK=./release/$${TARGET}.exe + diff -r 4cbf8c6a462d -r 8962f80f5d8e data/fileio/test/main.cpp --- a/data/fileio/test/main.cpp Tue Mar 11 17:30:35 2014 +0000 +++ b/data/fileio/test/main.cpp Tue Mar 11 17:32:31 2014 +0000 @@ -1,5 +1,16 @@ /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ -/* Copyright Chris Cannam - All Rights Reserved */ +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2013 Chris Cannam. + + 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 "AudioFileReaderTest.h" diff -r 4cbf8c6a462d -r 8962f80f5d8e data/fileio/test/test.pro --- a/data/fileio/test/test.pro Tue Mar 11 17:30:35 2014 +0000 +++ b/data/fileio/test/test.pro Tue Mar 11 17:32:31 2014 +0000 @@ -23,7 +23,7 @@ QT += network xml testlib QT -= gui -TARGET = svcore-test +TARGET = svcore-data-fileio-test DEPENDPATH += ../../.. INCLUDEPATH += ../../.. diff -r 4cbf8c6a462d -r 8962f80f5d8e data/model/DenseThreeDimensionalModel.h --- a/data/model/DenseThreeDimensionalModel.h Tue Mar 11 17:30:35 2014 +0000 +++ b/data/model/DenseThreeDimensionalModel.h Tue Mar 11 17:32:31 2014 +0000 @@ -83,6 +83,25 @@ virtual QString getBinName(size_t n) const = 0; /** + * Return true if the bins have values as well as names. If this + * returns true, getBinValue() may be used to retrieve the values. + */ + virtual bool hasBinValues() const { return false; } + + /** + * Return the value of bin n, if any. This is a "vertical scale" + * value which does not vary from one column to the next. This is + * only meaningful if hasBinValues() returns true. + */ + virtual float getBinValue(size_t n) const { return n; } + + /** + * Obtain the name of the unit of the values returned from + * getBinValue(), if any. + */ + virtual QString getBinValueUnit() const { return ""; } + + /** * Estimate whether a logarithmic scale might be appropriate for * the value scale. */ diff -r 4cbf8c6a462d -r 8962f80f5d8e data/model/EditableDenseThreeDimensionalModel.cpp --- a/data/model/EditableDenseThreeDimensionalModel.cpp Tue Mar 11 17:30:35 2014 +0000 +++ b/data/model/EditableDenseThreeDimensionalModel.cpp Tue Mar 11 17:32:31 2014 +0000 @@ -410,6 +410,37 @@ } bool +EditableDenseThreeDimensionalModel::hasBinValues() const +{ + return !m_binValues.empty(); +} + +float +EditableDenseThreeDimensionalModel::getBinValue(size_t n) const +{ + if (n < m_binValues.size()) return m_binValues[n]; + else return 0.f; +} + +void +EditableDenseThreeDimensionalModel::setBinValues(std::vector values) +{ + m_binValues = values; +} + +QString +EditableDenseThreeDimensionalModel::getBinValueUnit() const +{ + return m_binValueUnit; +} + +void +EditableDenseThreeDimensionalModel::setBinValueUnit(QString unit) +{ + m_binValueUnit = unit; +} + +bool EditableDenseThreeDimensionalModel::shouldUseLogValueScale() const { QReadLocker locker(&m_lock); diff -r 4cbf8c6a462d -r 8962f80f5d8e data/model/EditableDenseThreeDimensionalModel.h --- a/data/model/EditableDenseThreeDimensionalModel.h Tue Mar 11 17:30:35 2014 +0000 +++ b/data/model/EditableDenseThreeDimensionalModel.h Tue Mar 11 17:32:31 2014 +0000 @@ -127,10 +127,61 @@ */ virtual void setColumn(size_t x, const Column &values); + /** + * Return the name of bin n. This is a single label per bin that + * does not vary from one column to the next. + */ virtual QString getBinName(size_t n) const; + + /** + * Set the name of bin n. + */ virtual void setBinName(size_t n, QString); + + /** + * Set the names of all bins. + */ virtual void setBinNames(std::vector names); + /** + * Return true if the bins have values as well as names. (The + * values may have been derived from the names, e.g. by parsing + * numbers from them.) If this returns true, getBinValue() may be + * used to retrieve the values. + */ + virtual bool hasBinValues() const; + + /** + * Return the value of bin n, if any. This is a "vertical scale" + * value which does not vary from one column to the next. This is + * only meaningful if hasBinValues() returns true. + */ + virtual float getBinValue(size_t n) const; + + /** + * Set the values of all bins (separate from their labels). These + * are "vertical scale" values which do not vary from one column + * to the next. + */ + virtual void setBinValues(std::vector values); + + /** + * Obtain the name of the unit of the values returned from + * getBinValue(), if any. + */ + virtual QString getBinValueUnit() const; + + /** + * Set the name of the unit of the values return from + * getBinValue() if any. + */ + virtual void setBinValueUnit(QString unit); + + /** + * Return true if the distribution of values in the bins is such + * as to suggest a log scale (mapping to colour etc) may be better + * than a linear one. + */ bool shouldUseLogValueScale() const; virtual void setCompletion(int completion, bool update = true); @@ -162,6 +213,8 @@ Column expandAndRetrieve(size_t index) const; std::vector m_binNames; + std::vector m_binValues; + QString m_binValueUnit; size_t m_startFrame; size_t m_sampleRate; diff -r 4cbf8c6a462d -r 8962f80f5d8e transform/FeatureExtractionModelTransformer.cpp --- a/transform/FeatureExtractionModelTransformer.cpp Tue Mar 11 17:30:35 2014 +0000 +++ b/transform/FeatureExtractionModelTransformer.cpp Tue Mar 11 17:32:31 2014 +0000 @@ -278,12 +278,11 @@ //!!! the model rate to be the input model's rate, and adjust //!!! the resolution appropriately. We can't properly display //!!! data with a higher resolution than the base model at all -// modelRate = size_t(m_descriptors[n]->sampleRate + 0.001); if (m_descriptors[n]->sampleRate > input->getSampleRate()) { modelResolution = 1; } else { - modelResolution = size_t(input->getSampleRate() / - m_descriptors[n]->sampleRate); + modelResolution = size_t(round(input->getSampleRate() / + m_descriptors[n]->sampleRate)); } break; } @@ -852,7 +851,6 @@ // << ", m_descriptor->sampleRate = " << m_descriptor->sampleRate // << ", inputRate = " << inputRate // << " giving frame = "; - frame = lrintf((m_fixedRateFeatureNos[n] / m_descriptors[n]->sampleRate) * int(inputRate)); } @@ -927,7 +925,7 @@ } } - if (isOutput(n)) { // GF: added for flexi note model + if (isOutput(n)) { // GF: added for flexi note model float velocity = 100; if (feature.values.size() > index) { @@ -995,7 +993,14 @@ getConformingOutput(n); if (!model) return; - model->setColumn(frame / model->getResolution(), values); +// cerr << "(note: model resolution = " << model->getResolution() << ")" +// << endl; + + if (!feature.hasTimestamp && m_fixedRateFeatureNos[n] >= 0) { + model->setColumn(m_fixedRateFeatureNos[n], values); + } else { + model->setColumn(frame / model->getResolution(), values); + } } else { SVDEBUG << "FeatureExtractionModelTransformer::addFeature: Unknown output model type!" << endl;