# HG changeset patch # User Chris Cannam # Date 1499692999 -3600 # Node ID a533662c17f4647e2700e53811bdf91c0ff173db # Parent 09751743647e132cec557ef85654fe8436acae54# Parent e7e626a87a1ef03ba474c25a3c5ae886738c134e Merge from branch "scale-ticks" diff -r 09751743647e -r a533662c17f4 base/ScaleTickIntervals.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/ScaleTickIntervals.h Mon Jul 10 14:23:19 2017 +0100 @@ -0,0 +1,309 @@ +/* -*- 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-2017 Chris Cannam and 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. +*/ + +#ifndef SV_SCALE_TICK_INTERVALS_H +#define SV_SCALE_TICK_INTERVALS_H + +#include +#include +#include + +#include "LogRange.h" +#include "Debug.h" + +// Can't have this on by default, as we're called on every refresh +//#define DEBUG_SCALE_TICK_INTERVALS 1 + +class ScaleTickIntervals +{ +public: + struct Range { + double min; // start of value range + double max; // end of value range + int n; // number of divisions (approximate only) + }; + + struct Tick { + double value; // value this tick represents + std::string label; // value as written + }; + + typedef std::vector Ticks; + + /** + * Return a set of ticks that divide the range r linearly into + * roughly r.n equal divisions, in such a way as to yield + * reasonably human-readable labels. + */ + static Ticks linear(Range r) { + return linearTicks(r); + } + + /** + * Return a set of ticks that divide the range r into roughly r.n + * logarithmic divisions, in such a way as to yield reasonably + * human-readable labels. + */ + static Ticks logarithmic(Range r) { + LogRange::mapRange(r.min, r.max); + return logarithmicAlready(r); + } + + /** + * Return a set of ticks that divide the range r into roughly r.n + * logarithmic divisions, on the asssumption that r.min and r.max + * already represent the logarithms of the boundary values rather + * than the values themselves. + */ + static Ticks logarithmicAlready(Range r) { + return logTicks(r); + } + +private: + enum Display { + Fixed, + Scientific, + Auto + }; + + struct Instruction { + double initial; // value of first tick + double limit; // max from original range + double spacing; // increment between ticks + double roundTo; // what all displayed values should be rounded to + Display display; // whether to use fixed precision (%e, %f, or %g) + int precision; // number of dp (%f) or sf (%e) + bool logUnmap; // true if values represent logs of display values + }; + + static Instruction linearInstruction(Range r) + { + Display display = Auto; + + if (r.max < r.min) { + return linearInstruction({ r.max, r.min, r.n }); + } + if (r.n < 1 || r.max == r.min) { + return { r.min, r.min, 1.0, r.min, display, 1, false }; + } + + double inc = (r.max - r.min) / r.n; + + double digInc = log10(inc); + double digMax = log10(fabs(r.max)); + double digMin = log10(fabs(r.min)); + + int precInc = int(floor(digInc)); + double roundTo = pow(10.0, precInc); + + if (precInc > -4 && precInc < 4) { + display = Fixed; + } else if ((digMax >= -2.0 && digMax <= 3.0) && + (digMin >= -3.0 && digMin <= 3.0)) { + display = Fixed; + } else { + display = Scientific; + } + + int precRange = int(ceil(digMax - digInc)); + + int prec = 1; + + if (display == Fixed) { + if (digInc < 0) { + prec = -precInc; + } else { + prec = 0; + } + } else { + prec = precRange; + } + +#ifdef DEBUG_SCALE_TICK_INTERVALS + SVDEBUG << "ScaleTickIntervals: calculating linearInstruction" << endl + << "ScaleTickIntervals: min = " << r.min << ", max = " << r.max + << ", n = " << r.n << ", inc = " << inc << endl; + SVDEBUG << "ScaleTickIntervals: digMax = " << digMax + << ", digInc = " << digInc << endl; + SVDEBUG << "ScaleTickIntervals: display = " << display + << ", inc = " << inc << ", precInc = " << precInc + << ", precRange = " << precRange + << ", prec = " << prec << ", roundTo = " << roundTo + << endl; +#endif + + double min = r.min; + + if (roundTo != 0.0) { + inc = round(inc / roundTo) * roundTo; + if (inc < roundTo) inc = roundTo; + min = ceil(min / roundTo) * roundTo; + if (min > r.max) min = r.max; + } + + if (display == Scientific && min != 0.0) { + double digNewMin = log10(fabs(min)); + if (digNewMin < digInc) { + prec = int(ceil(digMax - digNewMin)); +#ifdef DEBUG_SCALE_TICK_INTERVALS + SVDEBUG << "ScaleTickIntervals: min is smaller than increment, adjusting prec to " << prec << endl; +#endif + } + } + + return { min, r.max, inc, roundTo, display, prec, false }; + } + + static Instruction logInstruction(Range r) + { + Display display = Auto; + + if (r.n < 1) { + return {}; + } + if (r.max < r.min) { + return logInstruction({ r.max, r.min, r.n }); + } + if (r.max == r.min) { + return { r.min, r.max, 1.0, r.min, display, 1, true }; + } + + double inc = (r.max - r.min) / r.n; + + double digInc = log10(inc); + int precInc = int(floor(digInc)); + double roundTo = pow(10.0, precInc); + + if (roundTo != 0.0) { + inc = round(inc / roundTo) * roundTo; + if (inc < roundTo) inc = roundTo; + } + + // if inc is close to giving us powers of two, nudge it + if (fabs(inc - 0.301) < 0.01) { + inc = log10(2.0); + } + + // smallest increment as displayed + double minDispInc = + LogRange::unmap(r.min + inc) - LogRange::unmap(r.min); + + int prec = 1; + + if (minDispInc > 0.0) { + prec = int(floor(log10(minDispInc))); + if (prec < 0) prec = -prec; + } + + if (r.max >= -2.0 && r.max <= 3.0 && + r.min >= -3.0 && r.min <= 3.0) { + display = Fixed; + if (prec == 0) prec = 1; + } + +#ifdef DEBUG_SCALE_TICK_INTERVALS + SVDEBUG << "ScaleTickIntervals: calculating logInstruction" << endl + << "ScaleTickIntervals: min = " << r.min << ", max = " << r.max + << ", n = " << r.n << ", inc = " << inc + << ", minDispInc = " << minDispInc << ", digInc = " << digInc + << endl; + SVDEBUG << "ScaleTickIntervals: display = " << display + << ", inc = " << inc << ", precInc = " << precInc + << ", prec = " << prec << endl; + SVDEBUG << "ScaleTickIntervals: roundTo = " << roundTo << endl; +#endif + + double min = r.min; + if (inc != 0.0) { + min = ceil(r.min / inc) * inc; + if (min > r.max) min = r.max; + } + + return { min, r.max, inc, 0.0, display, prec, true }; + } + + static Ticks linearTicks(Range r) { + Instruction instruction = linearInstruction(r); + Ticks ticks = explode(instruction); + return ticks; + } + + static Ticks logTicks(Range r) { + Instruction instruction = logInstruction(r); + Ticks ticks = explode(instruction); + return ticks; + } + + static Tick makeTick(Display display, int precision, double value) { + const int buflen = 40; + char buffer[buflen]; + snprintf(buffer, buflen, + display == Auto ? "%.*g" : + display == Fixed ? "%.*f" : + "%.*e", + precision, value); + return Tick({ value, std::string(buffer) }); + } + + static Ticks explode(Instruction instruction) { + +#ifdef DEBUG_SCALE_TICK_INTERVALS + SVDEBUG << "ScaleTickIntervals::explode:" << endl + << "initial = " << instruction.initial + << ", limit = " << instruction.limit + << ", spacing = " << instruction.spacing + << ", roundTo = " << instruction.roundTo + << ", display = " << instruction.display + << ", precision = " << instruction.precision + << ", logUnmap = " << instruction.logUnmap + << endl; +#endif + + if (instruction.spacing == 0.0) { + return {}; + } + + double eps = 1e-7; + if (instruction.spacing < eps * 10.0) { + eps = instruction.spacing / 10.0; + } + + double max = instruction.limit; + int n = 0; + + Ticks ticks; + + while (true) { + double value = instruction.initial + n * instruction.spacing; + if (value >= max + eps) { + break; + } + if (instruction.logUnmap) { + value = pow(10.0, value); + } + if (instruction.roundTo != 0.0) { + value = instruction.roundTo * round(value / instruction.roundTo); + } + ticks.push_back(makeTick(instruction.display, + instruction.precision, + value)); + ++n; + } + + return ticks; + } +}; + +#endif diff -r 09751743647e -r a533662c17f4 base/test/TestScaleTickIntervals.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/test/TestScaleTickIntervals.h Mon Jul 10 14:23:19 2017 +0100 @@ -0,0 +1,555 @@ +/* -*- 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_SCALE_TICK_INTERVALS_H +#define TEST_SCALE_TICK_INTERVALS_H + +#include "../ScaleTickIntervals.h" + +#include +#include +#include + +#include + +using namespace std; + +class TestScaleTickIntervals : public QObject +{ + Q_OBJECT + + void printDiff(vector ticks, + vector expected) { + + cerr << "Have " << ticks.size() << " ticks, expected " + << expected.size() << endl; + for (int i = 0; i < int(ticks.size()); ++i) { + cerr << i << ": have " << ticks[i].value << " \"" + << ticks[i].label << "\", expected "; + if (i < int(expected.size())) { + cerr << expected[i].value << " \"" << expected[i].label + << "\"" << endl; + } else { + cerr << "(n/a)" << endl; + } + } + } + + void compareTicks(ScaleTickIntervals::Ticks ticks, + ScaleTickIntervals::Ticks expected, + bool fuzzier = false) + { + double eps = 1e-7; + for (int i = 0; i < int(expected.size()); ++i) { + if (i < int(ticks.size())) { + bool pass = true; + if (ticks[i].label != expected[i].label) { + pass = false; + } else if (!fuzzier) { + if (fabs(ticks[i].value - expected[i].value) > eps) { + pass = false; + } + } else { + if (fabs(ticks[i].value - expected[i].value) > + fabs(ticks[i].value) * 1e-5) { + pass = false; + } + } + if (!pass) { + printDiff(ticks, expected); + QCOMPARE(ticks[i].label, expected[i].label); + QCOMPARE(ticks[i].value, expected[i].value); + } + } + } + if (ticks.size() != expected.size()) { + printDiff(ticks, expected); + } + QCOMPARE(ticks.size(), expected.size()); + } + +private slots: + void linear_0_1_10() + { + auto ticks = ScaleTickIntervals::linear({ 0, 1, 10 }); + ScaleTickIntervals::Ticks expected { + { 0.0, "0.0" }, + { 0.1, "0.1" }, + { 0.2, "0.2" }, + { 0.3, "0.3" }, + { 0.4, "0.4" }, + { 0.5, "0.5" }, + { 0.6, "0.6" }, + { 0.7, "0.7" }, + { 0.8, "0.8" }, + { 0.9, "0.9" }, + { 1.0, "1.0" } + }; + compareTicks(ticks, expected); + } + + void linear_0_5_5() + { + auto ticks = ScaleTickIntervals::linear({ 0, 5, 5 }); + ScaleTickIntervals::Ticks expected { + { 0, "0" }, + { 1, "1" }, + { 2, "2" }, + { 3, "3" }, + { 4, "4" }, + { 5, "5" }, + }; + compareTicks(ticks, expected); + } + + void linear_0_10_5() + { + auto ticks = ScaleTickIntervals::linear({ 0, 10, 5 }); + ScaleTickIntervals::Ticks expected { + { 0, "0" }, + { 2, "2" }, + { 4, "4" }, + { 6, "6" }, + { 8, "8" }, + { 10, "10" } + }; + compareTicks(ticks, expected); + } + + void linear_10_0_5() + { + auto ticks = ScaleTickIntervals::linear({ 10, 0, 5 }); + ScaleTickIntervals::Ticks expected { + { 0, "0" }, + { 2, "2" }, + { 4, "4" }, + { 6, "6" }, + { 8, "8" }, + { 10, "10" } + }; + compareTicks(ticks, expected); + } + + void linear_m10_0_5() + { + auto ticks = ScaleTickIntervals::linear({ -10, 0, 5 }); + ScaleTickIntervals::Ticks expected { + { -10, "-10" }, + { -8, "-8" }, + { -6, "-6" }, + { -4, "-4" }, + { -2, "-2" }, + { 0, "0" } + }; + compareTicks(ticks, expected); + } + + void linear_0_m10_5() + { + auto ticks = ScaleTickIntervals::linear({ 0, -10, 5 }); + ScaleTickIntervals::Ticks expected { + { -10, "-10" }, + { -8, "-8" }, + { -6, "-6" }, + { -4, "-4" }, + { -2, "-2" }, + { 0, "0" } + }; + compareTicks(ticks, expected); + } + + void linear_0_0p1_5() + { + auto ticks = ScaleTickIntervals::linear({ 0, 0.1, 5 }); + ScaleTickIntervals::Ticks expected { + { 0.00, "0.00" }, + { 0.02, "0.02" }, + { 0.04, "0.04" }, + { 0.06, "0.06" }, + { 0.08, "0.08" }, + { 0.10, "0.10" } + }; + compareTicks(ticks, expected); + } + + void linear_0_0p01_5() + { + auto ticks = ScaleTickIntervals::linear({ 0, 0.01, 5 }); + ScaleTickIntervals::Ticks expected { + { 0.000, "0.000" }, + { 0.002, "0.002" }, + { 0.004, "0.004" }, + { 0.006, "0.006" }, + { 0.008, "0.008" }, + { 0.010, "0.010" } + }; + compareTicks(ticks, expected); + } + + void linear_0_0p005_5() + { + auto ticks = ScaleTickIntervals::linear({ 0, 0.005, 5 }); + ScaleTickIntervals::Ticks expected { + { 0.000, "0.000" }, + { 0.001, "0.001" }, + { 0.002, "0.002" }, + { 0.003, "0.003" }, + { 0.004, "0.004" }, + { 0.005, "0.005" } + }; + compareTicks(ticks, expected); + } + + void linear_0_0p001_5() + { + auto ticks = ScaleTickIntervals::linear({ 0, 0.001, 5 }); + ScaleTickIntervals::Ticks expected { + { 0.0000, "0.0e+00" }, + { 0.0002, "2.0e-04" }, + { 0.0004, "4.0e-04" }, + { 0.0006, "6.0e-04" }, + { 0.0008, "8.0e-04" }, + { 0.0010, "1.0e-03" } + }; + compareTicks(ticks, expected); + } + + void linear_1_1p001_5() + { + auto ticks = ScaleTickIntervals::linear({ 1, 1.001, 5 }); + ScaleTickIntervals::Ticks expected { + { 1.0000, "1.0000" }, + { 1.0002, "1.0002" }, + { 1.0004, "1.0004" }, + { 1.0006, "1.0006" }, + { 1.0008, "1.0008" }, + { 1.0010, "1.0010" } + }; + compareTicks(ticks, expected); + } + + void linear_0p001_1_5() + { + auto ticks = ScaleTickIntervals::linear({ 0.001, 1, 5 }); + ScaleTickIntervals::Ticks expected { + { 0.1, "0.1" }, + { 0.3, "0.3" }, + { 0.5, "0.5" }, + { 0.7, "0.7" }, + { 0.9, "0.9" }, + }; + compareTicks(ticks, expected); + } + + void linear_10000_10010_5() + { + auto ticks = ScaleTickIntervals::linear({ 10000, 10010, 5 }); + ScaleTickIntervals::Ticks expected { + { 10000, "10000" }, + { 10002, "10002" }, + { 10004, "10004" }, + { 10006, "10006" }, + { 10008, "10008" }, + { 10010, "10010" }, + }; + compareTicks(ticks, expected); + } + + void linear_10000_20000_5() + { + auto ticks = ScaleTickIntervals::linear({ 10000, 20000, 5 }); + ScaleTickIntervals::Ticks expected { + { 10000, "10000" }, + { 12000, "12000" }, + { 14000, "14000" }, + { 16000, "16000" }, + { 18000, "18000" }, + { 20000, "20000" }, + }; + compareTicks(ticks, expected); + } + + void linear_m1_1_10() + { + auto ticks = ScaleTickIntervals::linear({ -1, 1, 10 }); + ScaleTickIntervals::Ticks expected { + { -1.0, "-1.0" }, + { -0.8, "-0.8" }, + { -0.6, "-0.6" }, + { -0.4, "-0.4" }, + { -0.2, "-0.2" }, + { 0.0, "0.0" }, + { 0.2, "0.2" }, + { 0.4, "0.4" }, + { 0.6, "0.6" }, + { 0.8, "0.8" }, + { 1.0, "1.0" } + }; + compareTicks(ticks, expected); + } + + void linear_221p23_623p7_57p4() + { + auto ticks = ScaleTickIntervals::linear({ 221.23, 623.7, 4 }); + // only 4 ticks, not 5, because none of the rounded tick + // values lies on an end value + ScaleTickIntervals::Ticks expected { + { 300, "300" }, + { 400, "400" }, + { 500, "500" }, + { 600, "600" }, + }; + compareTicks(ticks, expected); + } + + void linear_sqrt2_pi_7() + { + auto ticks = ScaleTickIntervals::linear({ sqrt(2.0), M_PI, 7 }); + // This would be better in steps of 0.25, but we only round to + // integral powers of ten + ScaleTickIntervals::Ticks expected { + { 1.5, "1.5" }, + { 1.7, "1.7" }, + { 1.9, "1.9" }, + { 2.1, "2.1" }, + { 2.3, "2.3" }, + { 2.5, "2.5" }, + { 2.7, "2.7" }, + { 2.9, "2.9" }, + { 3.1, "3.1" }, + }; + compareTicks(ticks, expected); + } + + void linear_pi_avogadro_7() + { + auto ticks = ScaleTickIntervals::linear({ M_PI, 6.022140857e23, 7 }); + ScaleTickIntervals::Ticks expected { + // not perfect, but ok-ish + { 1e+22, "1.00e+22" }, + { 1e+23, "1.00e+23" }, + { 1.9e+23, "1.90e+23" }, + { 2.8e+23, "2.80e+23" }, + { 3.7e+23, "3.70e+23" }, + { 4.6e+23, "4.60e+23" }, + { 5.5e+23, "5.50e+23" }, + }; + compareTicks(ticks, expected); + } + + void linear_2_3_1() + { + auto ticks = ScaleTickIntervals::linear({ 2, 3, 1 }); + ScaleTickIntervals::Ticks expected { + { 2.0, "2" }, + { 3.0, "3" } + }; + compareTicks(ticks, expected); + } + + void linear_2_3_2() + { + auto ticks = ScaleTickIntervals::linear({ 2, 3, 2 }); + ScaleTickIntervals::Ticks expected { + { 2.0, "2.0" }, + { 2.5, "2.5" }, + { 3.0, "3.0" } + }; + compareTicks(ticks, expected); + } + + void linear_2_3_3() + { + auto ticks = ScaleTickIntervals::linear({ 2, 3, 3 }); + ScaleTickIntervals::Ticks expected { + { 2.0, "2.0" }, + { 2.3, "2.3" }, + { 2.6, "2.6" }, + { 2.9, "2.9" } + }; + compareTicks(ticks, expected); + } + + void linear_2_3_4() + { + auto ticks = ScaleTickIntervals::linear({ 2, 3, 4 }); + // This would be better in steps of 0.25, but we only round to + // integral powers of ten + ScaleTickIntervals::Ticks expected { + { 2.0, "2.0" }, + { 2.3, "2.3" }, + { 2.6, "2.6" }, + { 2.9, "2.9" } + }; + compareTicks(ticks, expected); + } + + void linear_2_3_5() + { + auto ticks = ScaleTickIntervals::linear({ 2, 3, 5 }); + ScaleTickIntervals::Ticks expected { + { 2.0, "2.0" }, + { 2.2, "2.2" }, + { 2.4, "2.4" }, + { 2.6, "2.6" }, + { 2.8, "2.8" }, + { 3.0, "3.0" } + }; + compareTicks(ticks, expected); + } + + void linear_2_3_6() + { + auto ticks = ScaleTickIntervals::linear({ 2, 3, 6 }); + ScaleTickIntervals::Ticks expected { + { 2.0, "2.0" }, + { 2.2, "2.2" }, + { 2.4, "2.4" }, + { 2.6, "2.6" }, + { 2.8, "2.8" }, + { 3.0, "3.0" } + }; + compareTicks(ticks, expected); + } + + void linear_1_1_10() + { + // pathological range + auto ticks = ScaleTickIntervals::linear({ 1, 1, 10 }); + ScaleTickIntervals::Ticks expected { + { 1.0, "1" } + }; + compareTicks(ticks, expected); + } + + void linear_0_0_10() + { + // pathological range + auto ticks = ScaleTickIntervals::linear({ 0, 0, 10 }); + ScaleTickIntervals::Ticks expected { + { 0.0, "0" } + }; + compareTicks(ticks, expected); + } + + void linear_0_1_1() + { + auto ticks = ScaleTickIntervals::linear({ 0, 1, 1 }); + ScaleTickIntervals::Ticks expected { + { 0.0, "0" }, + { 1.0, "1" } + }; + compareTicks(ticks, expected); + } + + void linear_0_1_0() + { + // senseless input + auto ticks = ScaleTickIntervals::linear({ 0, 1, 0 }); + ScaleTickIntervals::Ticks expected { + { 0.0, "0" }, + }; + compareTicks(ticks, expected); + } + + void linear_0_1_m1() + { + // senseless input + auto ticks = ScaleTickIntervals::linear({ 0, 1, -1 }); + ScaleTickIntervals::Ticks expected { + { 0.0, "0" }, + }; + compareTicks(ticks, expected); + } + + void linear_0p465_778_10() + { + // a case that gave unsatisfactory results in real life + // (initially it had the first tick at 1) + auto ticks = ScaleTickIntervals::linear({ 0.465, 778.08, 10 }); + ScaleTickIntervals::Ticks expected { + { 10, "10" }, + { 90, "90" }, + { 170, "170" }, + { 250, "250" }, + { 330, "330" }, + { 410, "410" }, + { 490, "490" }, + { 570, "570" }, + { 650, "650" }, + { 730, "730" }, + }; + compareTicks(ticks, expected); + } + + void log_1_10_2() + { + auto ticks = ScaleTickIntervals::logarithmic({ 1, 10, 2 }); + ScaleTickIntervals::Ticks expected { + { 1.0, "1.0" }, + { pow(10.0, 0.5), "3.2" }, + { 10.0, "10.0" }, + }; + compareTicks(ticks, expected); + } + + void log_0_10_2() + { + auto ticks = ScaleTickIntervals::logarithmic({ 0, 10, 2 }); + ScaleTickIntervals::Ticks expected { + { 1e-6, "1e-06" }, + { 1, "1" }, + }; + compareTicks(ticks, expected); + } + + void log_pi_avogadro_7() + { + auto ticks = ScaleTickIntervals::logarithmic({ M_PI, 6.022140857e23, 7 }); + ScaleTickIntervals::Ticks expected { + { 1000, "1e+03" }, + { 1e+06, "1e+06" }, + { 1e+09, "1e+09" }, + { 1e+12, "1e+12" }, + { 1e+15, "1e+15" }, + { 1e+18, "1e+18" }, + { 1e+21, "1e+21" }, + }; + compareTicks(ticks, expected, true); + } + + void log_0p465_778_10() + { + auto ticks = ScaleTickIntervals::logarithmic({ 0.465, 778.08, 10 }); + ScaleTickIntervals::Ticks expected { + { 0.5, "0.5" }, + { 1, "1.0" }, + { 2, "2.0" }, + { 4, "4.0" }, + { 8, "8.0" }, + { 16, "16.0" }, + { 32, "32.0" }, + { 64, "64.0" }, + { 128, "128.0" }, + { 256, "256.0" }, + { 512, "512.0" }, + }; + compareTicks(ticks, expected); + } + +}; + +#endif + + diff -r 09751743647e -r a533662c17f4 base/test/files.pri --- a/base/test/files.pri Tue Mar 07 13:52:37 2017 +0000 +++ b/base/test/files.pri Mon Jul 10 14:23:19 2017 +0100 @@ -4,6 +4,7 @@ TestRangeMapper.h \ TestOurRealTime.h \ TestPitch.h \ + TestScaleTickIntervals.h \ TestStringBits.h \ TestVampRealTime.h diff -r 09751743647e -r a533662c17f4 base/test/svcore-base-test.cpp --- a/base/test/svcore-base-test.cpp Tue Mar 07 13:52:37 2017 +0000 +++ b/base/test/svcore-base-test.cpp Mon Jul 10 14:23:19 2017 +0100 @@ -14,6 +14,7 @@ #include "TestLogRange.h" #include "TestRangeMapper.h" #include "TestPitch.h" +#include "TestScaleTickIntervals.h" #include "TestStringBits.h" #include "TestOurRealTime.h" #include "TestVampRealTime.h" @@ -66,6 +67,11 @@ if (QTest::qExec(&t, argc, argv) == 0) ++good; else ++bad; } + { + TestScaleTickIntervals t; + if (QTest::qExec(&t, argc, argv) == 0) ++good; + else ++bad; + } if (bad > 0) { cerr << "\n********* " << bad << " test suite(s) failed!\n" << endl; diff -r 09751743647e -r a533662c17f4 files.pri --- a/files.pri Tue Mar 07 13:52:37 2017 +0000 +++ b/files.pri Mon Jul 10 14:23:19 2017 +0100 @@ -26,6 +26,7 @@ base/RecentFiles.h \ base/ResourceFinder.h \ base/RingBuffer.h \ + base/ScaleTickIntervals.h \ base/Scavenger.h \ base/Selection.h \ base/Serialiser.h \