Mercurial > hg > qm-dsp
changeset 451:3f913390bcf2
(Incomplete) unit tests for chroma and key estimation
author | Chris Cannam <c.cannam@qmul.ac.uk> |
---|---|
date | Thu, 23 May 2019 13:29:02 +0100 |
parents | 4852840b8a3c |
children | 684319f02921 1b89d1916cd5 |
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()