changeset 222:0990c1cb4049 chroma-key-tuning-review

(Incomplete) unit tests for chroma and key estimation
author Chris Cannam
date Thu, 23 May 2019 13:29:02 +0100
parents 04c2ac91c16a
children
files .travis.yml build/general/Makefile.inc tests/Makefile tests/TestChromagram.cpp tests/TestGetKeyMode.cpp
diffstat 5 files changed, 239 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/.travis.yml	Tue May 21 13:23:35 2019 +0100
+++ b/.travis.yml	Thu May 23 13:29:02 2019 +0100
@@ -19,6 +19,5 @@
       - valgrind
       
 script:
-  - make -f build/linux/Makefile.linux64
-  - ( cd tests ; make )
+  - make -f build/linux/Makefile.linux64 test
   
--- a/build/general/Makefile.inc	Tue May 21 13:23:35 2019 +0100
+++ b/build/general/Makefile.inc	Thu May 23 13:29:02 2019 +0100
@@ -144,6 +144,9 @@
 		$(AR) cr $@ $^
 		$(RANLIB) $@
 
+test:		$(LIBRARY)
+		$(MAKE) -C tests
+
 depend:
 		makedepend -fbuild/general/Makefile.inc -Y -- $(CFLAGS) -- $(SOURCES)
 
--- a/tests/Makefile	Tue May 21 13:23:35 2019 +0100
+++ b/tests/Makefile	Thu May 23 13:29:02 2019 +0100
@@ -5,12 +5,13 @@
 LDFLAGS := $(LDFLAGS) -lboost_unit_test_framework -lpthread
 LIBS := ../libqm-dsp.a
 
-TESTS	:= test-mathutilities test-window test-filter test-fft test-dct test-pvoc test-resampler test-medianfilter
+TESTS	:= test-mathutilities test-window test-filter test-fft test-dct test-pvoc test-resampler test-medianfilter test-getkeymode test-chromagram
 
 VG	:= valgrind -q
 
 all: $(TESTS)
-	for t in $(TESTS); do echo "Running $$t"; $(VG) ./"$$t" || exit 1; done
+#	for t in $(TESTS); do echo "Running $$t"; $(VG) ./"$$t" || exit 1; done
+	for t in $(TESTS); do echo "Running $$t"; ./"$$t" || exit 1; done
 
 test-medianfilter: TestMedianFilter.o $(LIBS)
 	$(CXX) -o $@ $^ $(LDFLAGS)
@@ -36,6 +37,12 @@
 test-resampler: TestResampler.o $(LIBS)
 	$(CXX) -o $@ $^ $(LDFLAGS)
 
+test-chromagram: TestChromagram.o $(LIBS)
+	$(CXX) -o $@ $^ $(LDFLAGS)
+
+test-getkeymode: TestGetKeyMode.o $(LIBS)
+	$(CXX) -o $@ $^ $(LDFLAGS)
+
 TestMathUtilities.o: $(LIBS)
 TestMedianFilter.o: $(LIBS)
 TestWindow.o: $(LIBS)
@@ -44,6 +51,8 @@
 TestDCT.o: $(LIBS)
 TestPhaseVocoder.o: $(LIBS)
 TestResampler.o: $(LIBS)
+TestChromagram.o: $(LIBS)
+TestGetKeyMode.o: $(LIBS)
 
 clean: 
 	rm -f *.o $(TESTS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/TestChromagram.cpp	Thu May 23 13:29:02 2019 +0100
@@ -0,0 +1,111 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+#include "dsp/chromagram/Chromagram.h"
+
+#include <iostream>
+
+#include <cmath>
+
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_MAIN
+
+#include <boost/test/unit_test.hpp>
+
+BOOST_AUTO_TEST_SUITE(TestChromagram)
+
+using std::cout;
+using std::endl;
+using std::string;
+using std::vector;
+
+string midiPitchName(int midiPitch)
+{
+    static string names[] = {
+        "C",  "C#", "D",  "D#",
+        "E",  "F",  "F#", "G",
+        "G#", "A",  "A#", "B"
+    };
+
+    return names[midiPitch % 12];
+}
+
+vector<double> generateSinusoid(double frequency,
+                                int sampleRate,
+                                int length)
+{
+    vector<double> buffer;
+    buffer.reserve(length);
+    for (int i = 0; i < length; ++i) {
+        buffer.push_back(sin(i * M_PI * 2.0 * frequency / sampleRate));
+    }
+    return buffer;
+}
+
+double frequencyForPitch(int midiPitch, double concertA)
+{
+    return concertA * pow(2.0, (midiPitch - 69.0) / 12.0);
+}
+
+BOOST_AUTO_TEST_CASE(sinusoid_12tET)
+{
+    double concertA = 440.0;
+    int sampleRate = 44100;
+    int bpo = 60;
+
+    ChromaConfig config {
+        sampleRate,
+        frequencyForPitch(36, concertA),
+        frequencyForPitch(108, concertA),
+        bpo,
+        0.0054,
+        MathUtilities::NormaliseNone
+    };
+    
+    Chromagram chroma(config);
+    
+    for (int midiPitch = 48; midiPitch < 96; ++midiPitch) {
+
+        cout << endl;
+
+        int blockSize = chroma.getFrameSize();
+        int hopSize = chroma.getHopSize();
+        cerr << "blockSize = " << blockSize
+             << ", hopSize = " << hopSize << endl;
+
+        double frequency = frequencyForPitch(midiPitch, concertA);
+        int expectedPeakBin = ((midiPitch - 36) * 5) % bpo;
+
+        cout << "midiPitch = " << midiPitch
+             << ", name = " << midiPitchName(midiPitch)
+             << ", frequency = " << frequency
+             << ", expected peak bin = "
+             << expectedPeakBin << endl;
+        
+        vector<double> signal = generateSinusoid(frequency,
+                                                 sampleRate,
+                                                 blockSize);
+
+        double *output = chroma.process(signal.data());
+
+        int peakBin = -1;
+        double peakValue = 0.0;
+
+        for (int i = 0; i < bpo; ++i) {
+            if (i == 0 || output[i] > peakValue) {
+                peakValue = output[i];
+                peakBin = i;
+            }
+        }
+
+        cout << "peak value = " << peakValue << " at bin " << peakBin << endl;
+        cout << "(neighbouring values are "
+             << (peakBin > 0 ? output[peakBin-1] : output[bpo-1])
+             << " and "
+             << (peakBin+1 < bpo ? output[peakBin+1] : output[0])
+             << ")" << endl;
+
+        BOOST_CHECK_EQUAL(peakBin, expectedPeakBin);
+    }
+}
+
+BOOST_AUTO_TEST_SUITE_END()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/TestGetKeyMode.cpp	Thu May 23 13:29:02 2019 +0100
@@ -0,0 +1,113 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+#include "dsp/keydetection/GetKeyMode.h"
+
+#include <iostream>
+
+#include <cmath>
+
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_MAIN
+
+#include <boost/test/unit_test.hpp>
+
+BOOST_AUTO_TEST_SUITE(TestGetKeyMode)
+
+using std::cout;
+using std::endl;
+using std::string;
+using std::vector;
+
+string keyName(int index, bool minor)
+{
+    static string namesMajor[] = {
+        "C", "Db", "D", "Eb",
+        "E", "F", "F# / Gb", "G",
+        "Ab", "A", "Bb", "B"
+    };
+
+    static string namesMinor[] = {
+        "C", "C#", "D", "Eb / D#",
+        "E", "F", "F#", "G",
+        "G#", "A", "Bb", "B"
+    };
+
+    if (index < 1 || index > 12) return "";
+
+    std::string name;
+    if (minor) name = namesMinor[index - 1] + " minor";
+    else name = namesMajor[index - 1] + " major";
+    return name;
+}
+
+string midiPitchName(int midiPitch)
+{
+    static string names[] = {
+        "C",  "C#", "D",  "D#",
+        "E",  "F",  "F#", "G",
+        "G#", "A",  "A#", "B"
+    };
+
+    return names[midiPitch % 12];
+}
+
+vector<double> generateSinusoid(double frequency,
+                                int sampleRate,
+                                int length)
+{
+    vector<double> buffer;
+    buffer.reserve(length);
+    for (int i = 0; i < length; ++i) {
+        buffer.push_back(sin(i * M_PI * 2.0 * frequency / sampleRate));
+    }
+    return buffer;
+}
+    
+BOOST_AUTO_TEST_CASE(sinusoid_12tET)
+{
+    double concertA = 440.0;
+    int sampleRate = 44100;
+    
+    for (int midiPitch = 48; midiPitch < 96; ++midiPitch) {
+
+        cout << endl;
+
+        GetKeyMode gkm(sampleRate, concertA, 10, 10);
+        int blockSize = gkm.getBlockSize();
+        int hopSize = gkm.getHopSize();
+        cerr << "blockSize = " << blockSize
+             << ", hopSize = " << hopSize << endl;
+
+        double frequency = concertA * pow(2.0, (midiPitch - 69.0) / 12.0);
+        cout << "midiPitch = " << midiPitch
+             << ", name = " << midiPitchName(midiPitch)
+             << ", frequency = " << frequency << endl;
+
+        int blocks = 4;
+        int totalLength = blockSize * blocks;
+        vector<double> signal = generateSinusoid(frequency, sampleRate,
+                                                 totalLength);
+
+        int key;
+        
+        for (int offset = 0; offset + blockSize < totalLength;
+             offset += hopSize) {
+            int k = gkm.process(signal.data() + offset);
+            if (offset == 0) {
+                key = k;
+            } else {
+                BOOST_CHECK_EQUAL(key, k);
+            }
+        }
+
+        bool minor = (key > 12);
+
+        int tonic = key;
+        if (minor) tonic -= 12;
+
+        string name = keyName(tonic, minor);
+        cout << "key value = " << key << ", name = " << name << endl;
+    }
+}
+
+BOOST_AUTO_TEST_SUITE_END()