--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ 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.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <cmath>
+#include "audio_filter.h"
+#include "audio_buffers.h"
+
+using std::vector;
+using std::min;
+using boost::shared_ptr;
+
+vector<float>
+AudioFilter::sinc_blackman (float cutoff, bool invert) const
+{
+ vector<float> ir (_M + 1);
+
+ /* Impulse response */
+
+ for (int i = 0; i <= _M; ++i) {
+ if (i == (_M / 2)) {
+ ir[i] = 2 * M_PI * cutoff;
+ } else {
+ /* sinc */
+ ir[i] = sin (2 * M_PI * cutoff * (i - _M / 2)) / (i - _M / 2);
+ /* Blackman window */
+ ir[i] *= (0.42 - 0.5 * cos (2 * M_PI * i / _M) + 0.08 * cos (4 * M_PI * i / _M));
+ }
+ }
+
+ /* Normalise */
+
+ float sum = 0;
+ for (int i = 0; i <= _M; ++i) {
+ sum += ir[i];
+ }
+
+ for (int i = 0; i <= _M; ++i) {
+ ir[i] /= sum;
+ }
+
+ /* Frequency inversion (swapping low-pass for high-pass, or whatever) */
+
+ if (invert) {
+ for (int i = 0; i <= _M; ++i) {
+ ir[i] = -ir[i];
+ }
+ ir[_M / 2] += 1;
+ }
+
+ return ir;
+}
+
+shared_ptr<AudioBuffers>
+AudioFilter::run (shared_ptr<AudioBuffers> in)
+{
+ shared_ptr<AudioBuffers> out (new AudioBuffers (in->channels(), in->frames()));
+
+ if (!_tail) {
+ _tail.reset (new AudioBuffers (in->channels(), _M + 1));
+ _tail->make_silent ();
+ }
+
+ for (int i = 0; i < in->channels(); ++i) {
+ for (int j = 0; j < in->frames(); ++j) {
+ float s = 0;
+ for (int k = 0; k <= _M; ++k) {
+ if ((j - k) < 0) {
+ s += _tail->data(i)[j - k + _M + 1] * _ir[k];
+ } else {
+ s += in->data(i)[j - k] * _ir[k];
+ }
+ }
+
+ out->data(i)[j] = s;
+ }
+ }
+
+ int const amount = min (in->frames(), _tail->frames());
+ if (amount < _tail->frames ()) {
+ _tail->move (amount, 0, _tail->frames() - amount);
+ }
+ _tail->copy_from (in.get(), amount, in->frames() - amount, _tail->frames () - amount);
+
+ return out;
+}
+
+LowPassAudioFilter::LowPassAudioFilter (float transition_bandwidth, float cutoff)
+ : AudioFilter (transition_bandwidth)
+{
+ _ir = sinc_blackman (cutoff, false);
+}
+
+
+HighPassAudioFilter::HighPassAudioFilter (float transition_bandwidth, float cutoff)
+ : AudioFilter (transition_bandwidth)
+{
+ _ir = sinc_blackman (cutoff, true);
+}
+
+BandPassAudioFilter::BandPassAudioFilter (float transition_bandwidth, float lower, float higher)
+ : AudioFilter (transition_bandwidth)
+{
+ vector<float> lpf = sinc_blackman (lower, false);
+ vector<float> hpf = sinc_blackman (higher, true);
+
+ _ir.resize (_M + 1);
+ for (int i = 0; i <= _M; ++i) {
+ _ir[i] = lpf[i] + hpf[i];
+ }
+
+ /* We now have a band-stop, so invert for band-pass */
+ for (int i = 0; i <= _M; ++i) {
+ _ir[i] = -_ir[i];
+ }
+
+ _ir[_M / 2] += 1;
+}
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ 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.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <vector>
+#include <boost/shared_ptr.hpp>
+
+class AudioBuffers;
+class audio_filter_impulse_kernel_test;
+class audio_filter_impulse_input_test;
+
+class AudioFilter
+{
+public:
+ AudioFilter (float transition_bandwidth)
+ {
+ _M = 4 / transition_bandwidth;
+ if (_M % 2) {
+ ++_M;
+ }
+ }
+
+ boost::shared_ptr<AudioBuffers> run (boost::shared_ptr<AudioBuffers> in);
+
+protected:
+ friend class audio_filter_impulse_kernel_test;
+ friend class audio_filter_impulse_input_test;
+
+ std::vector<float> sinc_blackman (float cutoff, bool invert) const;
+
+ std::vector<float> _ir;
+ int _M;
+ boost::shared_ptr<AudioBuffers> _tail;
+};
+
+class LowPassAudioFilter : public AudioFilter
+{
+public:
+ /** Construct a windowed-sinc low-pass filter using the Blackman window.
+ * @param transition_bandwidth Transition bandwidth as a fraction of the sampling rate.
+ * @param cutoff Cutoff frequency as a fraction of the sampling rate.
+ */
+ LowPassAudioFilter (float transition_bandwidth, float cutoff);
+};
+
+class HighPassAudioFilter : public AudioFilter
+{
+public:
+ /** Construct a windowed-sinc high-pass filter using the Blackman window.
+ * @param transition_bandwidth Transition bandwidth as a fraction of the sampling rate.
+ * @param cutoff Cutoff frequency as a fraction of the sampling rate.
+ */
+ HighPassAudioFilter (float transition_bandwidth, float cutoff);
+};
+
+class BandPassAudioFilter : public AudioFilter
+{
+public:
+ /** Construct a windowed-sinc band-pass filter using the Blackman window.
+ * @param transition_bandwidth Transition bandwidth as a fraction of the sampling rate.
+ * @param lower Lower cutoff frequency as a fraction of the sampling rate.
+ * @param higher Higher cutoff frequency as a fraction of the sampling rate.
+ */
+ BandPassAudioFilter (float transition_bandwidth, float lower, float higher);
+};
audio_buffers.cc
audio_content.cc
audio_decoder.cc
+ audio_filter.cc
audio_mapping.cc
audio_processor.cc
cinema.cc
single_stream_audio_content.cc
sndfile_content.cc
sndfile_decoder.cc
- sox_audio_processor.cc
subrip.cc
subrip_content.cc
subrip_decoder.cc
--- /dev/null
+/*
+ Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+
+ 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.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+/** @file test/audio_filter_test.cc
+ * @brief Basic tests of audio filters.
+ */
+
+#include <boost/test/unit_test.hpp>
+#include "lib/audio_filter.h"
+#include "lib/audio_buffers.h"
+
+using boost::shared_ptr;
+
+static void
+audio_filter_impulse_test_one (AudioFilter& f, int block_size, int num_blocks)
+{
+ int c = 0;
+
+ for (int i = 0; i < num_blocks; ++i) {
+
+ shared_ptr<AudioBuffers> in (new AudioBuffers (1, block_size));
+ for (int j = 0; j < block_size; ++j) {
+ in->data()[0][j] = c + j;
+ }
+
+ shared_ptr<AudioBuffers> out = f.run (in);
+
+ for (int j = 0; j < out->frames(); ++j) {
+ BOOST_CHECK_EQUAL (out->data()[0][j], c + j);
+ }
+
+ c += block_size;
+ }
+}
+
+/** Create a filter with an impulse as a kernel and check that it
+ * passes data through unaltered.
+ */
+BOOST_AUTO_TEST_CASE (audio_filter_impulse_kernel_test)
+{
+ AudioFilter f (0.02);
+ f._ir.resize (f._M + 1);
+
+ f._ir[0] = 1;
+ for (int i = 1; i <= f._M; ++i) {
+ f._ir[i] = 0;
+ }
+
+ audio_filter_impulse_test_one (f, 32, 1);
+ audio_filter_impulse_test_one (f, 256, 1);
+ audio_filter_impulse_test_one (f, 2048, 1);
+}
+
+/** Create filters and pass them impulses as input and check that
+ * the filter kernels comes back.
+ */
+BOOST_AUTO_TEST_CASE (audio_filter_impulse_input_test)
+{
+ LowPassAudioFilter lpf (0.02, 0.3);
+
+ shared_ptr<AudioBuffers> in (new AudioBuffers (1, 1751));
+ in->make_silent ();
+ in->data(0)[0] = 1;
+
+ shared_ptr<AudioBuffers> out = lpf.run (in);
+ for (int j = 0; j < out->frames(); ++j) {
+ if (j <= lpf._M) {
+ BOOST_CHECK_EQUAL (out->data(0)[j], lpf._ir[j]);
+ } else {
+ BOOST_CHECK_EQUAL (out->data(0)[j], 0);
+ }
+ }
+
+ HighPassAudioFilter hpf (0.02, 0.3);
+
+ in.reset (new AudioBuffers (1, 9133));
+ in->make_silent ();
+ in->data(0)[0] = 1;
+
+ out = hpf.run (in);
+ for (int j = 0; j < out->frames(); ++j) {
+ if (j <= hpf._M) {
+ BOOST_CHECK_EQUAL (out->data(0)[j], hpf._ir[j]);
+ } else {
+ BOOST_CHECK_EQUAL (out->data(0)[j], 0);
+ }
+ }
+}
audio_buffers_test.cc
audio_delay_test.cc
audio_decoder_test.cc
+ audio_filter_test.cc
audio_mapping_test.cc
black_fill_test.cc
burnt_subtitle_test.cc