Chris@1265: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
Chris@1265: 
Chris@1265: /*
Chris@1265:     Sonic Visualiser
Chris@1265:     An audio file viewer and annotation editor.
Chris@1265:     Centre for Digital Music, Queen Mary, University of London.
Chris@1265:     
Chris@1265:     This program is free software; you can redistribute it and/or
Chris@1265:     modify it under the terms of the GNU General Public License as
Chris@1265:     published by the Free Software Foundation; either version 2 of the
Chris@1265:     License, or (at your option) any later version.  See the file
Chris@1265:     COPYING included with this distribution for more information.
Chris@1265: */
Chris@1265: 
Chris@1265: #ifndef TEST_COLUMN_OP_H
Chris@1265: #define TEST_COLUMN_OP_H
Chris@1265: 
Chris@1265: #include "../ColumnOp.h"
Chris@1265: 
Chris@1265: #include <QObject>
Chris@1265: #include <QtTest>
Chris@1265: #include <QDir>
Chris@1265: 
Chris@1265: #include <iostream>
Chris@1265: 
Chris@1304: //#define REPORT 1
Chris@1304: 
Chris@1265: using namespace std;
Chris@1265: 
Chris@1265: class TestColumnOp : public QObject
Chris@1265: {
Chris@1265:     Q_OBJECT
Chris@1265: 
Chris@1265:     typedef ColumnOp C;
Chris@1265:     typedef ColumnOp::Column Column;
Chris@1266:     typedef vector<double> BinMapping;
Chris@1266: 
Chris@1269: #ifdef REPORT
Chris@1266:     template <typename T>
Chris@1266:     void report(vector<T> v) {
Chris@1266:         cerr << "Vector is: [ ";
Chris@1266:         for (int i = 0; i < int(v.size()); ++i) {
Chris@1266:             if (i > 0) cerr << ", ";
Chris@1266:             cerr << v[i];
Chris@1266:         }
Chris@1266:         cerr << " ]\n";
Chris@1266:     }
Chris@1269: #else
Chris@1269:     template <typename T>
Chris@1269:     void report(vector<T> ) { }
Chris@1269: #endif
Chris@1266:                                      
Chris@1265: private slots:
Chris@1265:     void applyGain() {
Chris@1265:         QCOMPARE(C::applyGain({}, 1.0), Column());
Chris@1265:         Column c { 1, 2, 3, -4, 5, 6 };
Chris@1265:         Column actual(C::applyGain(c, 1.5));
Chris@1265:         Column expected { 1.5, 3, 4.5, -6, 7.5, 9 };
Chris@1265:         QCOMPARE(actual, expected);
Chris@1265:         actual = C::applyGain(c, 1.0);
Chris@1265:         QCOMPARE(actual, c);
Chris@1265:         actual = C::applyGain(c, 0.0);
Chris@1265:         expected = { 0, 0, 0, 0, 0, 0 };
Chris@1265:         QCOMPARE(actual, expected);
Chris@1265:     }
Chris@1265: 
Chris@1265:     void fftScale() {
Chris@1265:         QCOMPARE(C::fftScale({}, 2.0), Column());
Chris@1265:         Column c { 1, 2, 3, -4, 5 };
Chris@1265:         Column actual(C::fftScale(c, 8));
Chris@1265:         Column expected { 0.25, 0.5, 0.75, -1, 1.25 };
Chris@1265:         QCOMPARE(actual, expected);
Chris@1265:     }
Chris@1265: 
Chris@1265:     void isPeak_null() {
Chris@1265:         QVERIFY(!C::isPeak({}, 0));
Chris@1265:         QVERIFY(!C::isPeak({}, 1));
Chris@1265:         QVERIFY(!C::isPeak({}, -1));
Chris@1265:     }
Chris@1265: 
Chris@1265:     void isPeak_obvious() {
Chris@1265:         Column c { 0.4, 0.5, 0.3 };
Chris@1265:         QVERIFY(!C::isPeak(c, 0));
Chris@1265:         QVERIFY(C::isPeak(c, 1));
Chris@1265:         QVERIFY(!C::isPeak(c, 2));
Chris@1265:     }
Chris@1265: 
Chris@1265:     void isPeak_edges() {
Chris@1265:         Column c { 0.5, 0.4, 0.3 };
Chris@1265:         QVERIFY(C::isPeak(c, 0));
Chris@1265:         QVERIFY(!C::isPeak(c, 1));
Chris@1265:         QVERIFY(!C::isPeak(c, 2));
Chris@1265:         QVERIFY(!C::isPeak(c, 3));
Chris@1265:         QVERIFY(!C::isPeak(c, -1));
Chris@1265:         c = { 1.4, 1.5 };
Chris@1265:         QVERIFY(!C::isPeak(c, 0));
Chris@1265:         QVERIFY(C::isPeak(c, 1));
Chris@1265:     }
Chris@1265: 
Chris@1265:     void isPeak_flat() {
Chris@1265:         Column c { 0.0, 0.0, 0.0 };
Chris@1265:         QVERIFY(C::isPeak(c, 0));
Chris@1265:         QVERIFY(!C::isPeak(c, 1));
Chris@1265:         QVERIFY(!C::isPeak(c, 2));
Chris@1265:     }
Chris@1265: 
Chris@1265:     void isPeak_mixedSign() {
Chris@1265:         Column c { 0.4, -0.5, -0.3, -0.6, 0.1, -0.3 };
Chris@1265:         QVERIFY(C::isPeak(c, 0));
Chris@1265:         QVERIFY(!C::isPeak(c, 1));
Chris@1265:         QVERIFY(C::isPeak(c, 2));
Chris@1265:         QVERIFY(!C::isPeak(c, 3));
Chris@1265:         QVERIFY(C::isPeak(c, 4));
Chris@1265:         QVERIFY(!C::isPeak(c, 5));
Chris@1265:     }
Chris@1265: 
Chris@1265:     void isPeak_duplicate() {
Chris@1265:         Column c({ 0.5, 0.5, 0.4, 0.4 });
Chris@1265:         QVERIFY(C::isPeak(c, 0));
Chris@1265:         QVERIFY(!C::isPeak(c, 1));
Chris@1265:         QVERIFY(!C::isPeak(c, 2));
Chris@1265:         QVERIFY(!C::isPeak(c, 3));
Chris@1265:         c = { 0.4, 0.4, 0.5, 0.5 };
Chris@1265:         QVERIFY(C::isPeak(c, 0)); // counterintuitive but necessary
Chris@1265:         QVERIFY(!C::isPeak(c, 1));
Chris@1265:         QVERIFY(C::isPeak(c, 2));
Chris@1265:         QVERIFY(!C::isPeak(c, 3));
Chris@1265:     }
Chris@1265: 
Chris@1265:     void peakPick() {
Chris@1265:         QCOMPARE(C::peakPick({}), Column());
Chris@1265:         Column c({ 0.5, 0.5, 0.4, 0.4 });
Chris@1265:         QCOMPARE(C::peakPick(c), Column({ 0.5, 0.0, 0.0, 0.0 }));
Chris@1265:         c = Column({ 0.4, -0.5, -0.3, -0.6, 0.1, -0.3 });
Chris@1265:         QCOMPARE(C::peakPick(c), Column({ 0.4, 0.0, -0.3, 0.0, 0.1, 0.0 }));
Chris@1265:     }
Chris@1265: 
Chris@1265:     void normalize_null() {
Chris@1265:         QCOMPARE(C::normalize({}, ColumnNormalization::None), Column());
Chris@1265:         QCOMPARE(C::normalize({}, ColumnNormalization::Sum1), Column());
Chris@1265:         QCOMPARE(C::normalize({}, ColumnNormalization::Max1), Column());
Chris@1265:         QCOMPARE(C::normalize({}, ColumnNormalization::Hybrid), Column());
Chris@1265:     }
Chris@1265: 
Chris@1265:     void normalize_none() {
Chris@1265:         Column c { 1, 2, 3, 4 };
Chris@1265:         QCOMPARE(C::normalize(c, ColumnNormalization::None), c);
Chris@1265:     }
Chris@1265: 
Chris@1266:     void normalize_none_mixedSign() {
Chris@1266:         Column c { 1, 2, -3, -4 };
Chris@1266:         QCOMPARE(C::normalize(c, ColumnNormalization::None), c);
Chris@1266:     }
Chris@1266: 
Chris@1265:     void normalize_sum1() {
Chris@1265:         Column c { 1, 2, 4, 3 };
Chris@1265:         QCOMPARE(C::normalize(c, ColumnNormalization::Sum1),
Chris@1265:                  Column({ 0.1, 0.2, 0.4, 0.3 }));
Chris@1265:     }
Chris@1265: 
Chris@1266:     void normalize_sum1_mixedSign() {
Chris@1266:         Column c { 1, 2, -4, -3 };
Chris@1266:         QCOMPARE(C::normalize(c, ColumnNormalization::Sum1),
Chris@1266:                  Column({ 0.1, 0.2, -0.4, -0.3 }));
Chris@1266:     }
Chris@1266: 
Chris@1265:     void normalize_max1() {
Chris@1265:         Column c { 4, 3, 2, 1 };
Chris@1265:         QCOMPARE(C::normalize(c, ColumnNormalization::Max1),
Chris@1265:                  Column({ 1.0, 0.75, 0.5, 0.25 }));
Chris@1265:     }
Chris@1265: 
Chris@1266:     void normalize_max1_mixedSign() {
Chris@1266:         Column c { -4, -3, 2, 1 };
Chris@1266:         QCOMPARE(C::normalize(c, ColumnNormalization::Max1),
Chris@1266:                  Column({ -1.0, -0.75, 0.5, 0.25 }));
Chris@1266:     }
Chris@1266: 
Chris@1265:     void normalize_hybrid() {
Chris@1265:         // with max == 99, log10(max+1) == 2 so scale factor will be 2/99
Chris@1265:         Column c { 22, 44, 99, 66 };
Chris@1265:         QCOMPARE(C::normalize(c, ColumnNormalization::Hybrid),
Chris@1265:                  Column({ 44.0/99.0, 88.0/99.0, 2.0, 132.0/99.0 }));
Chris@1265:     }
Chris@1265: 
Chris@1266:     void normalize_hybrid_mixedSign() {
Chris@1266:         // with max == 99, log10(max+1) == 2 so scale factor will be 2/99
Chris@1266:         Column c { 22, 44, -99, -66 };
Chris@1266:         QCOMPARE(C::normalize(c, ColumnNormalization::Hybrid),
Chris@1266:                  Column({ 44.0/99.0, 88.0/99.0, -2.0, -132.0/99.0 }));
Chris@1266:     }
Chris@1266:     
Chris@1266:     void distribute_simple() {
Chris@1266:         Column in { 1, 2, 3 };
Chris@1266:         BinMapping binfory { 0.0, 0.5, 1.0, 1.5, 2.0, 2.5 };
Chris@1266:         Column expected { 1, 1, 2, 2, 3, 3 };
Chris@1266:         Column actual(C::distribute(in, 6, binfory, 0, false));
Chris@1266:         report(actual);
Chris@1266:         QCOMPARE(actual, expected);
Chris@1266:     }
Chris@1266:     
Chris@1266:     void distribute_simple_interpolated() {
Chris@1266:         Column in { 1, 2, 3 };
Chris@1266:         BinMapping binfory { 0.0, 0.5, 1.0, 1.5, 2.0, 2.5 };
Chris@1267:         // There is a 0.5-bin offset from the distribution you might
Chris@1267:         // expect, because this corresponds visually to the way that
Chris@1267:         // bin values are duplicated upwards in simple_distribution.
Chris@1267:         // It means that switching between interpolated and
Chris@1267:         // non-interpolated views retains the visual position of each
Chris@1267:         // bin peak as somewhere in the middle of the scale area for
Chris@1267:         // that bin.
Chris@1267:         Column expected { 1, 1, 1.5, 2, 2.5, 3 };
Chris@1266:         Column actual(C::distribute(in, 6, binfory, 0, true));
Chris@1266:         report(actual);
Chris@1266:         QCOMPARE(actual, expected);
Chris@1266:     }
Chris@1266:     
Chris@1266:     void distribute_nonlinear() {
Chris@1266:         Column in { 1, 2, 3 };
Chris@1266:         BinMapping binfory { 0.0, 0.2, 0.5, 1.0, 2.0, 2.5 };
Chris@1266:         Column expected { 1, 1, 1, 2, 3, 3 };
Chris@1266:         Column actual(C::distribute(in, 6, binfory, 0, false));
Chris@1266:         report(actual);
Chris@1266:         QCOMPARE(actual, expected);
Chris@1266:     }
Chris@1266:     
Chris@1266:     void distribute_nonlinear_interpolated() {
Chris@1267:         // See distribute_simple_interpolated
Chris@1266:         Column in { 1, 2, 3 };
Chris@1266:         BinMapping binfory { 0.0, 0.2, 0.5, 1.0, 2.0, 2.5 };
Chris@1267:         Column expected { 1, 1, 1, 1.5, 2.5, 3 };
Chris@1266:         Column actual(C::distribute(in, 6, binfory, 0, true));
Chris@1266:         report(actual);
Chris@1266:         QCOMPARE(actual, expected);
Chris@1266:     }
Chris@1266:     
Chris@1266:     void distribute_shrinking() {
Chris@1266:         Column in { 4, 1, 2, 3, 5, 6 };
Chris@1266:         BinMapping binfory { 0.0, 2.0, 4.0 };
Chris@1266:         Column expected { 4, 3, 6 };
Chris@1266:         Column actual(C::distribute(in, 3, binfory, 0, false));
Chris@1266:         report(actual);
Chris@1266:         QCOMPARE(actual, expected);
Chris@1266:     }
Chris@1266:     
Chris@1266:     void distribute_shrinking_interpolated() {
Chris@1266:         // should be same as distribute_shrinking, we don't
Chris@1266:         // interpolate when resizing down
Chris@1266:         Column in { 4, 1, 2, 3, 5, 6 };
Chris@1266:         BinMapping binfory { 0.0, 2.0, 4.0 };
Chris@1266:         Column expected { 4, 3, 6 };
Chris@1266:         Column actual(C::distribute(in, 3, binfory, 0, true));
Chris@1266:         report(actual);
Chris@1266:         QCOMPARE(actual, expected);
Chris@1266:     }
Chris@1265:     
Chris@1304:     void distribute_nonlinear_someshrinking_interpolated() {
Chris@1304:         // But we *should* interpolate if the mapping involves
Chris@1304:         // shrinking some bins but expanding others.  See
Chris@1304:         // distribute_simple_interpolated for note on 0.5 offset
Chris@1304:         Column in { 4, 1, 2, 3, 5, 6 };
Chris@1304:         BinMapping binfory { 0.0, 3.0, 4.0, 4.5 };
Chris@1304:         Column expected { 4.0, 2.5, 4.0, 5.0 };
Chris@1304:         Column actual(C::distribute(in, 4, binfory, 0, true));
Chris@1304:         report(actual);
Chris@1304:         QCOMPARE(actual, expected);
Chris@1304:         binfory = BinMapping { 0.5, 1.0, 2.0, 5.0 };
Chris@1304:         expected = { 4.0, 2.5, 1.5, 5.5 };
Chris@1304:         actual = (C::distribute(in, 4, binfory, 0, true));
Chris@1304:         report(actual);
Chris@1304:         QCOMPARE(actual, expected);
Chris@1304:     }
Chris@1265: };
Chris@1265:     
Chris@1265: #endif
Chris@1265: