Mercurial > hg > qm-dsp
changeset 415:4ae4229a074a
Add DCT
author | Chris Cannam <c.cannam@qmul.ac.uk> |
---|---|
date | Wed, 07 Oct 2015 09:55:35 +0100 |
parents | 7e8d1f26b098 |
children | 1f3244a6884c |
files | .hgignore build/general/Makefile.inc dsp/transforms/DCT.cpp dsp/transforms/DCT.h dsp/transforms/FFT.h tests/Makefile tests/TestDCT.cpp |
diffstat | 7 files changed, 388 insertions(+), 2 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgignore Mon Sep 28 12:33:17 2015 +0100 +++ b/.hgignore Wed Oct 07 09:55:35 2015 +0100 @@ -5,5 +5,7 @@ *.a doc/html/ tests/test-* +tests/test.log *.rej +ext/uncertain/
--- a/build/general/Makefile.inc Mon Sep 28 12:33:17 2015 +0100 +++ b/build/general/Makefile.inc Wed Oct 07 09:55:35 2015 +0100 @@ -38,6 +38,7 @@ dsp/tonal/ChangeDetectionFunction.h \ dsp/tonal/TCSgram.h \ dsp/tonal/TonalEstimator.h \ + dsp/transforms/DCT.h \ dsp/transforms/FFT.h \ dsp/wavelet/Wavelet.h \ hmm/hmm.h \ @@ -83,6 +84,7 @@ dsp/tonal/ChangeDetectionFunction.cpp \ dsp/tonal/TCSgram.cpp \ dsp/tonal/TonalEstimator.cpp \ + dsp/transforms/DCT.cpp \ dsp/transforms/FFT.cpp \ dsp/wavelet/Wavelet.cpp \ hmm/hmm.c \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dsp/transforms/DCT.cpp Wed Oct 07 09:55:35 2015 +0100 @@ -0,0 +1,91 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + QM DSP Library + + Centre for Digital Music, Queen Mary, University of London. + This file by 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 "DCT.h" + +#include <cmath> + +DCT::DCT(int n) : + m_n(n), + m_scaled(n, 0.0), + m_time2(n*4, 0.0), + m_freq2r(n*4, 0.0), + m_freq2i(n*4, 0.0), + m_fft(n*4) +{ + m_scale = m_n * sqrt(2.0 / m_n); +} + +DCT::~DCT() +{ +} + +void +DCT::forward(const double *in, double *out) +{ + for (int i = 0; i < m_n; ++i) { + m_time2[i*2 + 1] = in[i]; + m_time2[m_n*4 - i*2 - 1] = in[i]; + } + + m_fft.forward(m_time2.data(), m_freq2r.data(), m_freq2i.data()); + + for (int i = 0; i < m_n; ++i) { + out[i] = m_freq2r[i]; + } +} + +void +DCT::forwardUnitary(const double *in, double *out) +{ + forward(in, out); + for (int i = 0; i < m_n; ++i) { + out[i] /= m_scale; + } + out[0] /= sqrt(2.0); +} + +void +DCT::inverse(const double *in, double *out) +{ + for (int i = 0; i < m_n; ++i) { + m_freq2r[i] = in[i]; + } + for (int i = 0; i < m_n; ++i) { + m_freq2r[m_n*2 - i] = -in[i]; + } + m_freq2r[m_n] = 0.0; + + for (int i = 0; i <= m_n*2; ++i) { + m_freq2i[i] = 0.0; + } + + m_fft.inverse(m_freq2r.data(), m_freq2i.data(), m_time2.data()); + + for (int i = 0; i < m_n; ++i) { + out[i] = m_time2[i*2 + 1]; + } +} + +void +DCT::inverseUnitary(const double *in, double *out) +{ + for (int i = 0; i < m_n; ++i) { + m_scaled[i] = in[i] * m_scale; + } + m_scaled[0] *= sqrt(2.0); + inverse(m_scaled.data(), out); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dsp/transforms/DCT.h Wed Oct 07 09:55:35 2015 +0100 @@ -0,0 +1,85 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + QM DSP Library + + Centre for Digital Music, Queen Mary, University of London. + This file by 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. +*/ + +#ifndef DCT_H +#define DCT_H + +#include "FFT.h" + +#include <vector> + +class DCT +{ +public: + /** + * Construct a DCT object to calculate the Discrete Cosine + * Transform given input of size n samples. The transform is + * implemented using an FFT of size 4n, for simplicity. + */ + DCT(int n); + + ~DCT(); + + /** + * Carry out a type-II DCT of size n, where n is the value + * provided to the constructor above. + * + * The in and out pointers must point to (enough space for) n + * values. + */ + void forward(const double *in, double *out); + + /** + * Carry out a type-II unitary DCT of size n, where n is the value + * provided to the constructor above. This is a scaled version of + * the type-II DCT corresponding to a transform with an orthogonal + * matrix. This is the transform implemented by the dct() function + * in MATLAB. + * + * The in and out pointers must point to (enough space for) n + * values. + */ + void forwardUnitary(const double *in, double *out); + + /** + * Carry out a type-III (inverse) DCT of size n, where n is the + * value provided to the constructor above. + * + * The in and out pointers must point to (enough space for) n + * values. + */ + void inverse(const double *in, double *out); + + /** + * Carry out a type-III (inverse) unitary DCT of size n, where n + * is the value provided to the constructor above. This is the + * inverse of forwardUnitary(). + * + * The in and out pointers must point to (enough space for) n + * values. + */ + void inverseUnitary(const double *in, double *out); + +private: + int m_n; + double m_scale; + std::vector<double> m_scaled; + std::vector<double> m_time2; + std::vector<double> m_freq2r; + std::vector<double> m_freq2i; + FFTReal m_fft; +}; + +#endif
--- a/dsp/transforms/FFT.h Mon Sep 28 12:33:17 2015 +0100 +++ b/dsp/transforms/FFT.h Wed Oct 07 09:55:35 2015 +0100 @@ -4,6 +4,12 @@ QM DSP Library 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 FFT_H
--- a/tests/Makefile Mon Sep 28 12:33:17 2015 +0100 +++ b/tests/Makefile Wed Oct 07 09:55:35 2015 +0100 @@ -1,11 +1,11 @@ CFLAGS := -I.. $(CFLAGS) -CXXFLAGS := -I.. -Wall -Wextra -g $(CXXFLAGS) +CXXFLAGS := -I.. -Wall -Wextra -std=c++11 -g $(CXXFLAGS) LDFLAGS := $(LDFLAGS) -lboost_unit_test_framework -lpthread LIBS := ../libqm-dsp.a -TESTS := test-mathutilities test-window test-fft test-pvoc test-resampler test-medianfilter +TESTS := test-mathutilities test-window test-fft test-dct test-pvoc test-resampler test-medianfilter VG := valgrind -q @@ -24,6 +24,9 @@ test-fft: TestFFT.o $(LIBS) $(CXX) -o $@ $^ $(LDFLAGS) +test-dct: TestDCT.o $(LIBS) + $(CXX) -o $@ $^ $(LDFLAGS) + test-pvoc: TestPhaseVocoder.o $(LIBS) $(CXX) -o $@ $^ $(LDFLAGS) @@ -34,6 +37,7 @@ TestMedianFilter.o: $(LIBS) TestWindow.o: $(LIBS) TestFFT.o: $(LIBS) +TestDCT.o: $(LIBS) TestPhaseVocoder.o: $(LIBS) TestResampler.o: $(LIBS)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/TestDCT.cpp Wed Oct 07 09:55:35 2015 +0100 @@ -0,0 +1,196 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +#include "dsp/transforms/DCT.h" + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MAIN + +#include <boost/test/unit_test.hpp> + +#include <stdexcept> + +using namespace std; + +BOOST_AUTO_TEST_SUITE(TestDCT) + +BOOST_AUTO_TEST_CASE(forwardArrayBounds) +{ + // initialise bins to something recognisable, so we can tell if + // they haven't been written; and allocate the inputs on the heap + // so that, if running under valgrind, we get warnings about + // overruns + double *in = new double[4]; + in[0] = 1; + in[1] = 1; + in[2] = -1; + in[3] = -1; + double out[] = { 999, 999, 999, 999, 999, 999 }; + DCT(4).forward(in, out+1); + // And check we haven't overrun the arrays + BOOST_CHECK_EQUAL(out[0], 999.0); + BOOST_CHECK_EQUAL(out[5], 999.0); + delete[] in; +} + +BOOST_AUTO_TEST_CASE(u_forwardArrayBounds) +{ + // initialise bins to something recognisable, so we can tell if + // they haven't been written; and allocate the inputs on the heap + // so that, if running under valgrind, we get warnings about + // overruns + double *in = new double[4]; + in[0] = 1; + in[1] = 1; + in[2] = -1; + in[3] = -1; + double out[] = { 999, 999, 999, 999, 999, 999 }; + DCT(4).forwardUnitary(in, out+1); + // And check we haven't overrun the arrays + BOOST_CHECK_EQUAL(out[0], 999.0); + BOOST_CHECK_EQUAL(out[5], 999.0); + delete[] in; +} + +BOOST_AUTO_TEST_CASE(inverseArrayBounds) +{ + // initialise bins to something recognisable, so we can tell if + // they haven't been written; and allocate the inputs on the heap + // so that, if running under valgrind, we get warnings about + // overruns + double *in = new double[4]; + in[0] = 1; + in[1] = 1; + in[2] = -1; + in[3] = -1; + double out[] = { 999, 999, 999, 999, 999, 999 }; + DCT(4).inverse(in, out+1); + // And check we haven't overrun the arrays + BOOST_CHECK_EQUAL(out[0], 999.0); + BOOST_CHECK_EQUAL(out[5], 999.0); + delete[] in; + +} + +BOOST_AUTO_TEST_CASE(u_inverseArrayBounds) +{ + // initialise bins to something recognisable, so we can tell if + // they haven't been written; and allocate the inputs on the heap + // so that, if running under valgrind, we get warnings about + // overruns + double *in = new double[4]; + in[0] = 1; + in[1] = 1; + in[2] = -1; + in[3] = -1; + double out[] = { 999, 999, 999, 999, 999, 999 }; + DCT(4).inverseUnitary(in, out+1); + // And check we haven't overrun the arrays + BOOST_CHECK_EQUAL(out[0], 999.0); + BOOST_CHECK_EQUAL(out[5], 999.0); + delete[] in; +} + +BOOST_AUTO_TEST_CASE(nonU4) +{ + int n = 4; + vector<double> in { 1, 2, 3, 5 }; + vector<double> out(n); + vector<double> inv(n); + vector<double> expected { 22.0, -8.1564, 1.4142, -1.2137 }; + + DCT d(n); + + // our expected values are very approximate + double thresh = 1e-4; + + d.forward(in.data(), out.data()); + + for (int i = 0; i < n; ++i) { + BOOST_CHECK_SMALL(expected[i] - out[i], thresh); + } + + d.inverse(out.data(), inv.data()); + + for (int i = 0; i < n; ++i) { + BOOST_CHECK_SMALL(in[i] - inv[i], thresh); + } + + // do it again, just in case some internal state was modified in inverse + + d.forward(in.data(), out.data()); + + for (int i = 0; i < n; ++i) { + BOOST_CHECK_SMALL(expected[i] - out[i], thresh); + } +} + +BOOST_AUTO_TEST_CASE(u4) +{ + int n = 4; + vector<double> in { 1, 2, 3, 5 }; + vector<double> out(n); + vector<double> inv(n); + vector<double> expected { 5.5, -2.8837, 0.5, -0.4291 }; + + DCT d(n); + + // our expected values are very approximate + double thresh = 1e-4; + + d.forwardUnitary(in.data(), out.data()); + + for (int i = 0; i < n; ++i) { + BOOST_CHECK_SMALL(expected[i] - out[i], thresh); + } + + d.inverseUnitary(out.data(), inv.data()); + + for (int i = 0; i < n; ++i) { + BOOST_CHECK_SMALL(in[i] - inv[i], thresh); + } + + // do it again, just in case some internal state was modified in inverse + + d.forwardUnitary(in.data(), out.data()); + + for (int i = 0; i < n; ++i) { + BOOST_CHECK_SMALL(expected[i] - out[i], thresh); + } +} + +BOOST_AUTO_TEST_CASE(u5) +{ + int n = 5; + vector<double> in { 1, 2, 3, 5, 6 }; + vector<double> out(n); + vector<double> inv(n); + vector<double> expected { 7.6026, -4.1227, 0.3162, -0.0542, -0.3162 }; + + DCT d(n); + + // our expected values are very approximate + double thresh = 1e-4; + + d.forwardUnitary(in.data(), out.data()); + + for (int i = 0; i < n; ++i) { + BOOST_CHECK_SMALL(expected[i] - out[i], thresh); + } + + d.inverseUnitary(out.data(), inv.data()); + + for (int i = 0; i < n; ++i) { + BOOST_CHECK_SMALL(in[i] - inv[i], thresh); + } + + // do it again, just in case some internal state was modified in inverse + + d.forwardUnitary(in.data(), out.data()); + + for (int i = 0; i < n; ++i) { + BOOST_CHECK_SMALL(expected[i] - out[i], thresh); + } +} + +BOOST_AUTO_TEST_SUITE_END() +