M .hgignore +6 -0
@@ 3,3 3,9 @@ syntax: glob
*.o
*.a
test-*
+example/resample
+test/e2e/halve
+test/e2e/random
+test/e2e/undulating
+test/e2e/oversample
+test/e2e/out
M Makefile +10 -8
@@ 1,19 1,20 @@
# Add to RESAMPLE_DEFINES the relevant options for your desired
-# third-party library support.
+# implementation and/or third-party library support.
#
-# Available options are
+# Available library options are
#
# -DHAVE_LIBSAMPLERATE The libsamplerate library is available (recommended)
# -DHAVE_IPP Intel's Integrated Performance Primitives are available
# -DHAVE_LIBRESAMPLE The libresample library is available
+# -DUSE_BQRESAMPLER Compile the built-in BQ resampler (pretty good)
# -DUSE_SPEEX Compile the built-in Speex-derived resampler
#
-# You may define more than one of these. If you define USE_SPEEX, the
-# code will be compiled in and will be used when it is judged to be
-# the best available option for a given quality setting. If no flags
-# are supplied, the code will refuse to compile.
-
+# You may define more than one of these, and the implementation used
+# will depend on the quality setting you request - but it is usually
+# better to stick with a single known library. If no flags are
+# supplied, the code will refuse to compile.
+#
RESAMPLE_DEFINES := -DHAVE_LIBSAMPLERATE
@@ 25,7 26,8 @@ VECTOR_DEFINES :=
ALLOCATOR_DEFINES :=
-# Add any related includes and libraries here
+# Add any related includes and libraries here (e.g. -lsamplerate in
+# THIRD_PARTY_LIBS if using libsamplerate)
#
THIRD_PARTY_INCLUDES :=
THIRD_PARTY_LIBS := -lsamplerate
M README.md +4 -8
@@ 3,13 3,9 @@ bqresample
==========
A small C++ library wrapping various audio sample rate conversion
-libraries.
-
-Covers libsamplerate, Intel IPP, libresample, and Speex resampler
-implementations. Note however that libsamplerate is now the
-recommended option on every platform, so for new applications there is
-probably no good reason not to use it directly. Suitable for Windows,
-Mac, and Linux.
+libraries. Contains a built-in implementation and wrappers for
+libsamplerate, Intel IPP, and Speex resampler
+implementations. Suitable for Windows, Mac, and Linux.
Requires the bqvec library.
@@ 18,7 14,7 @@ same authors (see https://hg.sr.ht/~brea
It has been pulled out into a separate library and relicensed under a
more permissive licence.
-C++ standard required: C++98 (does not use C++11 or newer features)
+C++ standard required: C++11
* To compile: read and follow the notes in Makefile, edit the Makefile,
then make test. Or else use one of the pre-edited Makefiles in the
M bqresample/Resampler.h +38 -0
@@ 44,6 44,9 @@ class Resampler
{
public:
enum Quality { Best, FastestTolerable, Fastest };
+ enum Dynamism { RatioOftenChanging, RatioMostlyFixed };
+ enum RatioChange { SmoothRatioChange, SuddenRatioChange };
+
enum Exception { ImplementationError };
struct Parameters {
@@ 53,6 56,22 @@ public:
*/
Quality quality;
+ /**
+ * Performance hint indicating whether the ratio is expected
+ * to change regularly or not. If not, more work may happen on
+ * ratio changes to reduce work when ratio is unchanged.
+ */
+ Dynamism dynamism;
+
+ /**
+ * Hint indicating whether to smooth transitions, via filter
+ * interpolation or some such method, at ratio change
+ * boundaries, or whether to make a precise switch to the new
+ * ratio without regard to audible artifacts. The actual
+ * effect of this depends on the implementation in use.
+ */
+ RatioChange ratioChange;
+
/**
* Rate of expected input prior to resampling: may be used to
* determine the filter bandwidth for the quality setting. If
@@ 79,6 98,8 @@ public:
Parameters() :
quality(FastestTolerable),
+ dynamism(RatioMostlyFixed),
+ ratioChange(SmoothRatioChange),
initialSampleRate(44100),
maxBufferSize(0),
debugLevel(0) { }
@@ 137,8 158,25 @@ public:
double ratio,
bool final = false);
+ /**
+ * Return the channel count provided on construction.
+ */
int getChannelCount() const;
+ /**
+ * Return the ratio that will be actually used when the given
+ * ratio is requested. For example, if the resampler internally
+ * uses a rational approximation of the given ratio, this will
+ * return the closest double to that approximation. Not all
+ * implementations support this; an implementation that does not
+ * will just return the given ratio.
+ */
+ double getEffectiveRatio(double ratio) const;
+
+ /**
+ * Reset the internal processing state so that the next call
+ * behaves as if the resampler had just been constructed.
+ */
void reset();
class Impl;
M build/Makefile.inc +29 -6
@@ 1,6 1,7 @@
SRC_DIR := src
TEST_DIR := test
+EXAMPLE_DIR := example
SPEEX_DIR := speex
HEADER_DIR := bqresample
@@ 10,9 11,12 @@ HEADERS := $(wildcard $(HEADER_DIR)/*.h)
OBJECTS := $(SOURCES:.cpp=.o)
OBJECTS := $(OBJECTS:.c=.o)
-TEST_SOURCES := $(wildcard $(TEST_DIR)/*.cpp)
+TEST_SOURCES := $(wildcard $(TEST_DIR)/*.cpp) $(wildcard $(TEST_DIR)/e2e/*.cpp)
TEST_OBJECTS := $(TEST_SOURCES:.cpp=.o)
+EXAMPLE_SOURCES := $(wildcard $(EXAMPLE_DIR)/*.cpp)
+EXAMPLE_OBJECTS := $(EXAMPLE_SOURCES:.cpp=.o)
+
OPTFLAGS ?= -O3 -ffast-math
CXXFLAGS ?= -std=c++98 -fpic -Wall -Wextra -Werror $(RESAMPLE_DEFINES) $(VECTOR_DEFINES) $(ALLOCATOR_DEFINES) $(OPTFLAGS) -I. $(THIRD_PARTY_INCLUDES) -I../bqvec
@@ 23,8 27,15 @@ LIBRARY := libbqresample.a
all: $(LIBRARY)
-test: $(LIBRARY) test-resampler
+test: $(LIBRARY) test-resampler test/e2e/halve test/e2e/oversample test/e2e/random test/e2e/undulating
./test-resampler
+ mkdir -p ./test/e2e/out
+ ./test/e2e/halve ./test/e2e/sweep-log.wav ./test/e2e/out/halve.wav
+ ./test/e2e/oversample ./test/e2e/sweep-log.wav ./test/e2e/out/oversample.wav
+ ./test/e2e/random ./test/e2e/sweep-log.wav ./test/e2e/out/random.wav
+ ./test/e2e/undulating ./test/e2e/sweep-log.wav ./test/e2e/out/undulating.wav
+
+example: $(LIBRARY) example/resample
valgrind: $(LIBRARY) test-resampler
valgrind ./test-resampler
@@ 35,17 46,29 @@ valgrind: $(LIBRARY) test-resampler
test-resampler: test/TestResampler.o $(LIBRARY)
$(CXX) $(CXXFLAGS) -o $@ $^ $(LIBRARY) -lboost_unit_test_framework -L../bqvec -lbqvec $(THIRD_PARTY_LIBS)
+example/resample: example/resample.o $(LIBRARY)
+ $(CXX) $(CXXFLAGS) -o $@ $^ $(LIBRARY) -lsndfile -L../bqvec -lbqvec $(THIRD_PARTY_LIBS)
+
+test/e2e/%: test/e2e/%.o $(LIBRARY)
+ $(CXX) $(CXXFLAGS) -o $@ $^ $(LIBRARY) -lsndfile -L../bqvec -lbqvec $(THIRD_PARTY_LIBS)
+
clean:
- rm -f $(OBJECTS) $(TEST_OBJECTS)
+ rm -f $(OBJECTS) $(TEST_OBJECTS) $(EXAMPLE_OBJECTS)
distclean: clean
- rm -f $(LIBRARY) test-resampler
+ rm -f $(LIBRARY) test-resampler test/e2e/halve test/e2e/oversample test/e2e/oversample test/e2e/random test/e2e/undulating
depend:
- makedepend -Y -fbuild/Makefile.inc $(SOURCES) $(HEADERS) $(TEST_SOURCES)
+ makedepend $(RESAMPLE_DEFINES) -Y -fbuild/Makefile.inc $(SOURCES) $(HEADERS) $(TEST_SOURCES) $(EXAMPLE_SOURCES)
# DO NOT DELETE
-src/Resampler.o: bqresample/Resampler.h
+src/BQResampler.o: src/BQResampler.h
+src/Resampler.o: bqresample/Resampler.h src/BQResampler.h
test/TestResampler.o: bqresample/Resampler.h
+test/e2e/halve.o: bqresample/Resampler.h test/e2e/e2e.cpp
+test/e2e/oversample.o: bqresample/Resampler.h test/e2e/e2e.cpp
+test/e2e/random.o: bqresample/Resampler.h test/e2e/e2e.cpp
+test/e2e/undulating.o: bqresample/Resampler.h test/e2e/e2e.cpp
+example/resample.o: bqresample/Resampler.h
A => example/resample.cpp +163 -0
@@ 0,0 1,163 @@
+
+#include "bqresample/Resampler.h"
+
+#include <sndfile.h>
+
+#include <iostream>
+
+#include <cstring>
+#include <cstdlib>
+#include <cmath>
+
+using namespace std;
+
+void usage()
+{
+ cerr << "Usage: resample [-v] [-c <converter>] -to <rate> <infile> <outfile>" << endl;
+ cerr << "where <converter> may be 0, 1, or 2, for best, medium, or fastest respectively." << endl;
+ cerr << "The default converter is 0, best." << endl;
+ cerr << "Supply -v for verbose output." << endl;
+ exit(2);
+}
+
+int main(int argc, char **argv)
+{
+ double target = 0.0;
+ int quality = 0;
+ int arg;
+ bool verbose = false;
+
+ for (arg = 1; arg + 2 < argc; ++arg) {
+ if (!strcmp(argv[arg], "-c")) {
+ char *e = argv[arg+1];
+ quality = strtol(argv[arg+1], &e, 10);
+ if (*e || (quality < 0) || (quality > 2)) {
+ cerr << "error: invalid converter \""
+ << argv[arg+1] << "\" (must be 0, 1, 2)" << endl;
+ usage();
+ }
+ ++arg;
+ continue;
+ } else if (!strcmp(argv[arg], "-to")) {
+ target = strtod(argv[arg+1], 0);
+ if (!target) {
+ cerr << "error: invalid target \"" << argv[arg+1]
+ << "\" (must be numeric)" << endl;
+ usage();
+ }
+ ++arg;
+ continue;
+ } else if (!strcmp(argv[arg], "-v")) {
+ verbose = true;
+ } else {
+ cerr << "error: unexpected option \"" << argv[arg] << "\"" << endl;
+ usage();
+ }
+ }
+
+ if (!target || arg + 2 != argc) {
+ usage();
+ }
+
+ string infilename = argv[arg];
+ string outfilename = argv[arg+1];
+
+ SF_INFO info_in;
+ SNDFILE *file_in = sf_open(infilename.c_str(), SFM_READ, &info_in);
+ if (!file_in) {
+ cerr << "failed to open " << infilename << endl;
+ return 1;
+ }
+
+ int output_rate = round(target);
+ int channels = info_in.channels;
+
+ if (verbose) {
+ cerr << "input rate = " << info_in.samplerate << endl;
+ cerr << "output rate = " << output_rate << endl;
+ }
+
+ double ratio = target / info_in.samplerate;
+ if (verbose) {
+ cerr << "ratio = " << ratio << endl;
+ }
+
+ breakfastquay::Resampler::Parameters parameters;
+ switch (quality) {
+ case 0:
+ parameters.quality = breakfastquay::Resampler::Best;
+ if (verbose) {
+ cerr << "quality = best" << endl;
+ }
+ break;
+ case 1:
+ parameters.quality = breakfastquay::Resampler::FastestTolerable;
+ if (verbose) {
+ cerr << "quality = middling" << endl;
+ }
+ break;
+ case 2:
+ parameters.quality = breakfastquay::Resampler::Fastest;
+ if (verbose) {
+ cerr << "quality = worst" << endl;
+ }
+ break;
+ }
+
+ SF_INFO info_out;
+ memset(&info_out, 0, sizeof(SF_INFO));
+ info_out.channels = channels;
+ info_out.format = info_in.format;
+ info_out.samplerate = output_rate;
+ SNDFILE *file_out = sf_open(outfilename.c_str(), SFM_WRITE, &info_out);
+ if (!file_out) {
+ cerr << "failed to open " << outfilename << endl;
+ return 1;
+ }
+
+ int ibs = 1024;
+ int obs = ceil(ibs * ratio);
+ float *ibuf = new float[ibs * channels];
+ float *obuf = new float[obs * channels];
+
+ parameters.dynamism = breakfastquay::Resampler::RatioMostlyFixed;
+ parameters.ratioChange = breakfastquay::Resampler::SuddenRatioChange;
+ parameters.initialSampleRate = info_in.samplerate;
+ parameters.debugLevel = (verbose ? 1 : 0);
+ breakfastquay::Resampler resampler(parameters, info_in.channels);
+
+ int n = 0;
+ while (true) {
+ int count = sf_readf_float(file_in, ibuf, ibs);
+ if (verbose) {
+ cerr << ".";
+ }
+ if (count < 0) {
+ cerr << "error: count = " << count << endl;
+ break;
+ }
+ bool final = (count < ibs);
+ int got = resampler.resampleInterleaved
+ (obuf, obs, ibuf, count, ratio, final);
+ if (got == 0 && final) {
+ break;
+ } else {
+ for (int i = 0; i < got * channels; ++i) {
+ if (obuf[i] < -1.0) obuf[i] = -1.0;
+ if (obuf[i] > 1.0) obuf[i] = 1.0;
+ }
+ sf_writef_float(file_out, obuf, got);
+ }
+ ++n;
+ }
+
+ if (verbose) {
+ cerr << endl;
+ }
+
+ sf_close(file_in);
+ sf_close(file_out);
+
+ delete[] ibuf;
+ delete[] obuf;
+}
A => src/BQResampler.cpp +656 -0
@@ 0,0 1,656 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ bqresample
+
+ A small library wrapping various audio sample rate conversion
+ implementations in C++.
+
+ Copyright 2007-2021 Particular Programs Ltd.
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation
+ files (the "Software"), to deal in the Software without
+ restriction, including without limitation the rights to use, copy,
+ modify, merge, publish, distribute, sublicense, and/or sell copies
+ of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ Except as contained in this notice, the names of Chris Cannam and
+ Particular Programs Ltd shall not be used in advertising or
+ otherwise to promote the sale, use or other dealings in this
+ Software without prior written authorization.
+*/
+
+#include "BQResampler.h"
+
+#include <cmath>
+
+#include <iostream>
+
+#include <bqvec/Allocators.h>
+#include <bqvec/VectorOps.h>
+
+using std::vector;
+using std::cerr;
+using std::endl;
+using std::min;
+using std::max;
+
+namespace breakfastquay {
+
+BQResampler::BQResampler(Parameters parameters, int channels) :
+ m_qparams(parameters.quality),
+ m_dynamism(parameters.dynamism),
+ m_ratio_change(parameters.ratioChange),
+ m_debug_level(parameters.debugLevel),
+ m_initial_rate(parameters.referenceSampleRate),
+ m_channels(channels),
+ m_fade_count(0),
+ m_initialised(false)
+{
+ if (m_debug_level > 0) {
+ cerr << "BQResampler::BQResampler: "
+ << (m_dynamism == RatioOftenChanging ? "often-changing" : "mostly-fixed")
+ << ", "
+ << (m_ratio_change == SmoothRatioChange ? "smooth" : "sudden")
+ << " ratio changes, ref " << m_initial_rate << " Hz" << endl;
+ }
+
+ if (m_dynamism == RatioOftenChanging) {
+ m_proto_length = m_qparams.proto_p * m_qparams.p_multiple + 1;
+ if (m_debug_level > 0) {
+ cerr << "BQResampler: creating prototype filter of length "
+ << m_proto_length << endl;
+ }
+ m_prototype = make_filter(m_proto_length, m_qparams.proto_p);
+ m_prototype.push_back(0.0); // interpolate without fear
+ }
+
+ int phase_reserve = 2 * int(round(m_initial_rate));
+ int buffer_reserve = 1000 * m_channels;
+ m_state_a.phase_info.reserve(phase_reserve);
+ m_state_a.buffer.reserve(buffer_reserve);
+
+ if (m_dynamism == RatioOftenChanging) {
+ m_state_b.phase_info.reserve(phase_reserve);
+ m_state_b.buffer.reserve(buffer_reserve);
+ }
+
+ m_s = &m_state_a;
+ m_fade = &m_state_b;
+}
+
+BQResampler::BQResampler(const BQResampler &other) :
+ m_qparams(other.m_qparams),
+ m_dynamism(other.m_dynamism),
+ m_ratio_change(other.m_ratio_change),
+ m_debug_level(other.m_debug_level),
+ m_initial_rate(other.m_initial_rate),
+ m_channels(other.m_channels),
+ m_state_a(other.m_state_a),
+ m_state_b(other.m_state_b),
+ m_fade_count(other.m_fade_count),
+ m_prototype(other.m_prototype),
+ m_proto_length(other.m_proto_length),
+ m_initialised(other.m_initialised)
+{
+ if (other.m_s == &(other.m_state_a)) {
+ m_s = &m_state_a;
+ m_fade = &m_state_b;
+ } else {
+ m_s = &m_state_b;
+ m_fade = &m_state_a;
+ }
+}
+
+void
+BQResampler::reset()
+{
+ m_initialised = false;
+ m_fade_count = 0;
+}
+
+BQResampler::QualityParams::QualityParams(Quality q)
+{
+ switch (q) {
+ case Fastest:
+ p_multiple = 12;
+ proto_p = 160;
+ k_snr = 70.0;
+ k_transition = 0.2;
+ cut = 0.9;
+ break;
+ case FastestTolerable:
+ p_multiple = 62;
+ proto_p = 160;
+ k_snr = 90.0;
+ k_transition = 0.05;
+ cut = 0.975;
+ break;
+ case Best:
+ p_multiple = 122;
+ proto_p = 800;
+ k_snr = 100.0;
+ k_transition = 0.01;
+ cut = 0.995;
+ break;
+ }
+}
+
+int
+BQResampler::resampleInterleaved(float *const out,
+ int outspace,
+ const float *const in,
+ int incount,
+ double ratio,
+ bool final) {
+
+ int fade_length = round(m_initial_rate / 1000.0);
+ if (fade_length < 6) {
+ fade_length = 6;
+ }
+ int max_fade = min(outspace, int(floor(incount * ratio))) / 2;
+ if (fade_length > max_fade) {
+ fade_length = max_fade;
+ }
+
+ if (!m_initialised) {
+ state_for_ratio(*m_s, ratio, *m_fade);
+ m_initialised = true;
+ } else if (ratio != m_s->parameters.ratio) {
+ state *tmp = m_fade;
+ m_fade = m_s;
+ m_s = tmp;
+ state_for_ratio(*m_s, ratio, *m_fade);
+ if (m_ratio_change == SmoothRatioChange) {
+ if (m_debug_level > 0) {
+ cerr << "BQResampler: ratio changed, beginning fade of length "
+ << fade_length << endl;
+ }
+ m_fade_count = fade_length;
+ }
+ }
+
+ int i = 0, o = 0;
+ int bufsize = m_s->buffer.size();
+
+ int incount_samples = incount * m_channels;
+ int outspace_samples = outspace * m_channels;
+
+ while (o < outspace_samples) {
+ while (i < incount_samples && m_s->fill < bufsize) {
+ m_s->buffer[m_s->fill++] = in[i++];
+ }
+ if (m_s->fill == bufsize) {
+ out[o++] = reconstruct_one(m_s);
+ } else if (final && m_s->fill > m_s->centre) {
+ out[o++] = reconstruct_one(m_s);
+ } else if (final && m_s->fill == m_s->centre &&
+ m_s->current_phase != m_s->initial_phase) {
+ out[o++] = reconstruct_one(m_s);
+ } else {
+ break;
+ }
+ }
+
+ int fbufsize = m_fade->buffer.size();
+ int fi = 0, fo = 0;
+ while (fo < o && m_fade_count > 0) {
+ while (fi < incount_samples && m_fade->fill < fbufsize) {
+ m_fade->buffer[m_fade->fill++] = in[fi++];
+ }
+ if (m_fade->fill == fbufsize) {
+ double r = reconstruct_one(m_fade);
+ double fadeWith = out[fo];
+ double extent = double(m_fade_count - 1) / double(fade_length);
+ double mixture = 0.5 * (1.0 - cos(M_PI * extent));
+ double mixed = r * mixture + fadeWith * (1.0 - mixture);
+ out[fo] = mixed;
+ ++fo;
+ if (m_fade->current_channel == 0) {
+ --m_fade_count;
+ }
+ } else {
+ break;
+ }
+ }
+
+ return o / m_channels;
+}
+
+double
+BQResampler::getEffectiveRatio(double ratio) const {
+ if (m_initialised && ratio == m_s->parameters.ratio) {
+ return m_s->parameters.effective;
+ } else {
+ return pick_params(ratio).effective;
+ }
+}
+
+int
+BQResampler::gcd(int a, int b) const
+{
+ int c = a % b;
+ if (c == 0) return b;
+ else return gcd(b, c);
+}
+
+double
+BQResampler::bessel0(double x) const
+{
+ static double facsquared[] = {
+ 0.0, 1.0, 4.0, 36.0,
+ 576.0, 14400.0, 518400.0, 25401600.0,
+ 1625702400.0, 131681894400.0, 1.316818944E13, 1.59335092224E15,
+ 2.29442532803E17, 3.87757880436E19, 7.60005445655E21,
+ 1.71001225272E24, 4.37763136697E26, 1.26513546506E29,
+ 4.09903890678E31, 1.47975304535E34
+ };
+ static int nterms = sizeof(facsquared) / sizeof(facsquared[0]);
+ double b = 1.0;
+ for (int n = 1; n < nterms; ++n) {
+ double ff = facsquared[n];
+ double term = pow(x / 2.0, n * 2.0) / ff;
+ b += term;
+ }
+ return b;
+}
+
+vector<double>
+BQResampler::kaiser(double beta, int len) const
+{
+ double denominator = bessel0(beta);
+ int half = (len % 2 == 0 ? len/2 : (len+1)/2);
+ vector<double> v(len, 0.0);
+ for (int n = 0; n < half; ++n) {
+ double k = (2.0 * n) / (len-1) - 1.0;
+ v[n] = bessel0 (beta * sqrt(1.0 - k*k)) / denominator;
+ }
+ for (int n = half; n < len; ++n) {
+ v[n] = v[len-1 - n];
+ }
+ return v;
+}
+
+void
+BQResampler::kaiser_params(double attenuation,
+ double transition,
+ double &beta,
+ int &len) const
+{
+ if (attenuation > 21.0) {
+ len = 1 + ceil((attenuation - 7.95) / (2.285 * transition));
+ } else {
+ len = 1 + ceil(5.79 / transition);
+ }
+ beta = 0.0;
+ if (attenuation > 50.0) {
+ beta = 0.1102 * (attenuation - 8.7);
+ } else if (attenuation > 21.0) {
+ beta = 0.5842 * (pow (attenuation - 21.0, 0.4)) +
+ 0.07886 * (attenuation - 21.0);
+ }
+}
+
+vector<double>
+BQResampler::kaiser_for(double attenuation,
+ double transition,
+ int minlen,
+ int maxlen) const
+{
+ double beta;
+ int m;
+ kaiser_params(attenuation, transition, beta, m);
+ int mb = m;
+ if (maxlen > 0 && mb > maxlen - 1) {
+ mb = maxlen - 1;
+ } else if (minlen > 0 && mb < minlen) {
+ mb = minlen;
+ }
+ if (mb % 2 == 0) ++mb;
+ if (m_debug_level > 0) {
+ cerr << "BQResampler: window attenuation " << attenuation
+ << ", transition " << transition
+ << " -> length " << m << " adjusted to " << mb
+ << ", beta " << beta << endl;
+ }
+ return kaiser(beta, mb);
+}
+
+void
+BQResampler::sinc_multiply(double peak_to_zero, vector<double> &buf) const
+{
+ int len = int(buf.size());
+ if (len < 2) return;
+
+ int left = len / 2;
+ int right = (len + 1) / 2;
+ double m = M_PI / peak_to_zero;
+
+ for (int i = 1; i <= right; ++i) {
+ double x = i * m;
+ double sinc = sin(x) / x;
+ if (i <= left) {
+ buf[left - i] *= sinc;
+ }
+ if (i < right) {
+ buf[i + left] *= sinc;
+ }
+ }
+}
+
+BQResampler::params
+BQResampler::fill_params(double ratio, int num, int denom) const
+{
+ params p;
+ int g = gcd (num, denom);
+ p.ratio = ratio;
+ p.numerator = num / g;
+ p.denominator = denom / g;
+ p.effective = double(p.numerator) / double(p.denominator);
+ p.peak_to_zero = max(p.denominator, p.numerator);
+ p.peak_to_zero /= m_qparams.cut;
+ p.scale = double(p.numerator) / double(p.peak_to_zero);
+
+ if (m_debug_level > 0) {
+ cerr << "BQResampler: ratio " << p.ratio
+ << " -> fraction " << p.numerator << "/" << p.denominator
+ << " with error " << p.effective - p.ratio
+ << endl;
+ cerr << "BQResampler: peak-to-zero " << p.peak_to_zero
+ << ", scale " << p.scale
+ << endl;
+ }
+
+ return p;
+}
+
+BQResampler::params
+BQResampler::pick_params(double ratio) const
+{
+ // Farey algorithm, see
+ // https://www.johndcook.com/blog/2010/10/20/best-rational-approximation/
+ int max_denom = 192000;
+ double a = 0.0, b = 1.0, c = 1.0, d = 0.0;
+ double pa = a, pb = b, pc = c, pd = d;
+ double eps = 1e-9;
+ while (b <= max_denom && d <= max_denom) {
+ double mediant = (a + c) / (b + d);
+ if (fabs(ratio - mediant) < eps) {
+ if (b + d <= max_denom) {
+ return fill_params(ratio, a + c, b + d);
+ } else if (d > b) {
+ return fill_params(ratio, c, d);
+ } else {
+ return fill_params(ratio, a, b);
+ }
+ }
+ if (ratio > mediant) {
+ pa = a; pb = b;
+ a += c; b += d;
+ } else {
+ pc = c; pd = d;
+ c += a; d += b;
+ }
+ }
+ if (fabs(ratio - (pc / pd)) < fabs(ratio - (pa / pb))) {
+ return fill_params(ratio, pc, pd);
+ } else {
+ return fill_params(ratio, pa, pb);
+ }
+}
+
+void
+BQResampler::phase_data_for(vector<BQResampler::phase_rec> &target_phase_data,
+ floatbuf &target_phase_sorted_filter,
+ int filter_length,
+ const vector<double> *filter,
+ int initial_phase,
+ int input_spacing,
+ int output_spacing) const
+{
+ target_phase_data.clear();
+ target_phase_data.reserve(input_spacing);
+
+ for (int p = 0; p < input_spacing; ++p) {
+ int next_phase = p - output_spacing;
+ while (next_phase < 0) next_phase += input_spacing;
+ next_phase %= input_spacing;
+ double dspace = double(input_spacing);
+ int zip_length = ceil(double(filter_length - p) / dspace);
+ int drop = ceil(double(max(0, output_spacing - p)) / dspace);
+ phase_rec phase;
+ phase.next_phase = next_phase;
+ phase.drop = drop;
+ phase.length = zip_length;
+ phase.start_index = 0; // we fill this in below if needed
+ target_phase_data.push_back(phase);
+ }
+
+ if (m_dynamism == RatioMostlyFixed) {
+ if (!filter) throw std::logic_error("filter required at phase_data_for in RatioMostlyFixed mode");
+ target_phase_sorted_filter.clear();
+ target_phase_sorted_filter.reserve(filter_length);
+ for (int p = initial_phase; ; ) {
+ phase_rec &phase = target_phase_data[p];
+ phase.start_index = target_phase_sorted_filter.size();
+ for (int i = 0; i < phase.length; ++i) {
+ target_phase_sorted_filter.push_back
+ ((*filter)[i * input_spacing + p]);
+ }
+ p = phase.next_phase;
+ if (p == initial_phase) {
+ break;
+ }
+ }
+ }
+}
+
+vector<double>
+BQResampler::make_filter(int filter_length, double peak_to_zero) const
+{
+ vector<double> filter;
+ filter.reserve(filter_length);
+
+ vector<double> kaiser = kaiser_for(m_qparams.k_snr, m_qparams.k_transition,
+ 1, filter_length);
+ int k_length = kaiser.size();
+
+ if (k_length == filter_length) {
+ sinc_multiply(peak_to_zero, kaiser);
+ return kaiser;
+ } else {
+ kaiser.push_back(0.0);
+ double m = double(k_length - 1) / double(filter_length - 1);
+ for (int i = 0; i < filter_length; ++i) {
+ double ix = i * m;
+ int iix = floor(ix);
+ double remainder = ix - iix;
+ double value = 0.0;
+ value += kaiser[iix] * (1.0 - remainder);
+ value += kaiser[iix+1] * remainder;
+ filter.push_back(value);
+ }
+ sinc_multiply(peak_to_zero, filter);
+ return filter;
+ }
+}
+
+void
+BQResampler::state_for_ratio(BQResampler::state &target_state,
+ double ratio,
+ const BQResampler::state &BQ_R__ prev_state) const
+{
+ params parameters = pick_params(ratio);
+ target_state.parameters = parameters;
+
+ target_state.filter_length =
+ int(parameters.peak_to_zero * m_qparams.p_multiple + 1);
+
+ if (target_state.filter_length % 2 == 0) {
+ ++target_state.filter_length;
+ }
+
+ int half_length = target_state.filter_length / 2; // nb length is odd
+ int input_spacing = parameters.numerator;
+ int initial_phase = half_length % input_spacing;
+
+ target_state.initial_phase = initial_phase;
+ target_state.current_phase = initial_phase;
+
+ if (m_dynamism == RatioMostlyFixed) {
+
+ if (m_debug_level > 0) {
+ cerr << "BQResampler: creating filter of length "
+ << target_state.filter_length << endl;
+ }
+
+ vector<double> filter =
+ make_filter(target_state.filter_length, parameters.peak_to_zero);
+
+ phase_data_for(target_state.phase_info,
+ target_state.phase_sorted_filter,
+ target_state.filter_length, &filter,
+ target_state.initial_phase,
+ input_spacing,
+ parameters.denominator);
+ } else {
+ phase_data_for(target_state.phase_info,
+ target_state.phase_sorted_filter,
+ target_state.filter_length, 0,
+ target_state.initial_phase,
+ input_spacing,
+ parameters.denominator);
+ }
+
+ int buffer_left = half_length / input_spacing;
+ int buffer_right = buffer_left + 1;
+
+ int buffer_length = buffer_left + buffer_right;
+ buffer_length = max(buffer_length,
+ int(prev_state.buffer.size() / m_channels));
+
+ target_state.centre = buffer_length / 2;
+ target_state.left = target_state.centre - buffer_left;
+ target_state.fill = target_state.centre;
+
+ buffer_length *= m_channels;
+ target_state.centre *= m_channels;
+ target_state.left *= m_channels;
+ target_state.fill *= m_channels;
+
+ int n_phases = int(target_state.phase_info.size());
+
+ if (m_debug_level > 0) {
+ cerr << "BQResampler: " << m_channels << " channel(s) interleaved"
+ << ", buffer left " << buffer_left
+ << ", right " << buffer_right
+ << ", total " << buffer_length << endl;
+
+ cerr << "BQResampler: input spacing " << input_spacing
+ << ", output spacing " << parameters.denominator
+ << ", initial phase " << initial_phase
+ << " of " << n_phases << endl;
+ }
+
+ if (prev_state.buffer.size() > 0) {
+ if (int(prev_state.buffer.size()) == buffer_length) {
+ target_state.buffer = prev_state.buffer;
+ target_state.fill = prev_state.fill;
+ } else {
+ target_state.buffer = floatbuf(buffer_length, 0.0);
+ for (int i = 0; i < prev_state.fill; ++i) {
+ int offset = i - prev_state.centre;
+ int new_ix = offset + target_state.centre;
+ if (new_ix >= 0 && new_ix < buffer_length) {
+ target_state.buffer[new_ix] = prev_state.buffer[i];
+ target_state.fill = new_ix + 1;
+ }
+ }
+ }
+
+ int phases_then = int(prev_state.phase_info.size());
+ double distance_through =
+ double(prev_state.current_phase) / double(phases_then);
+ target_state.current_phase = round(n_phases * distance_through);
+ if (target_state.current_phase >= n_phases) {
+ target_state.current_phase = n_phases - 1;
+ }
+ } else {
+ target_state.buffer = floatbuf(buffer_length, 0.0);
+ }
+}
+
+double
+BQResampler::reconstruct_one(state *s) const
+{
+ const phase_rec &pr = s->phase_info[s->current_phase];
+ int phase_length = pr.length;
+ double result = 0.0;
+
+ int dot_length =
+ min(phase_length,
+ (int(s->buffer.size()) - s->left) / m_channels);
+
+ if (m_dynamism == RatioMostlyFixed) {
+ int phase_start = pr.start_index;
+ if (m_channels == 1) {
+ result = v_multiply_and_sum
+ (s->phase_sorted_filter.data() + phase_start,
+ s->buffer.data() + s->left,
+ dot_length);
+ } else {
+ for (int i = 0; i < dot_length; ++i) {
+ result +=
+ s->phase_sorted_filter[phase_start + i] *
+ s->buffer[s->left + i * m_channels + s->current_channel];
+ }
+ }
+ } else {
+ double m = double(m_proto_length - 1) / double(s->filter_length - 1);
+ for (int i = 0; i < dot_length; ++i) {
+ double sample =
+ s->buffer[s->left + i * m_channels + s->current_channel];
+ int filter_index = i * s->parameters.numerator + s->current_phase;
+ double proto_index = m * filter_index;
+ int iix = floor(proto_index);
+ double remainder = proto_index - iix;
+ double filter_value = m_prototype[iix] * (1.0 - remainder);
+ filter_value += m_prototype[iix+1] * remainder;
+ result += filter_value * sample;
+ }
+ }
+
+ s->current_channel = (s->current_channel + 1) % m_channels;
+
+ if (s->current_channel == 0) {
+
+ if (pr.drop > 0) {
+ int drop = pr.drop * m_channels;
+ v_move(s->buffer.data(), s->buffer.data() + drop,
+ int(s->buffer.size()) - drop);
+ for (int i = 1; i <= drop; ++i) {
+ s->buffer[s->buffer.size() - i] = 0.0;
+ }
+ s->fill -= drop;
+ }
+
+ s->current_phase = pr.next_phase;
+ }
+
+ return result * s->parameters.scale;
+}
+
+}
A => src/BQResampler.h +179 -0
@@ 0,0 1,179 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+
+/*
+ bqresample
+
+ A small library wrapping various audio sample rate conversion
+ implementations in C++.
+
+ Copyright 2007-2021 Particular Programs Ltd.
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation
+ files (the "Software"), to deal in the Software without
+ restriction, including without limitation the rights to use, copy,
+ modify, merge, publish, distribute, sublicense, and/or sell copies
+ of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ Except as contained in this notice, the names of Chris Cannam and
+ Particular Programs Ltd shall not be used in advertising or
+ otherwise to promote the sale, use or other dealings in this
+ Software without prior written authorization.
+*/
+
+#ifndef BQ_BQRESAMPLER_H
+#define BQ_BQRESAMPLER_H
+
+#include <vector>
+
+#include <bqvec/Allocators.h>
+#include <bqvec/Restrict.h>
+
+namespace breakfastquay {
+
+class BQResampler
+{
+public:
+ enum Quality { Best, FastestTolerable, Fastest };
+ enum Dynamism { RatioOftenChanging, RatioMostlyFixed };
+ enum RatioChange { SmoothRatioChange, SuddenRatioChange };
+
+ struct Parameters {
+ Quality quality;
+ Dynamism dynamism;
+ RatioChange ratioChange;
+ double referenceSampleRate;
+ int debugLevel;
+
+ Parameters() :
+ quality(FastestTolerable),
+ dynamism(RatioMostlyFixed),
+ ratioChange(SmoothRatioChange),
+ referenceSampleRate(44100),
+ debugLevel(0) { }
+ };
+
+ BQResampler(Parameters parameters, int channels);
+ BQResampler(const BQResampler &);
+
+ int resampleInterleaved(float *const out, int outspace,
+ const float *const in, int incount,
+ double ratio, bool final);
+
+ double getEffectiveRatio(double ratio) const;
+
+ void reset();
+
+private:
+ struct QualityParams {
+ int p_multiple;
+ int proto_p;
+ double k_snr;
+ double k_transition;
+ double cut;
+ QualityParams(Quality);
+ };
+
+ const QualityParams m_qparams;
+ const Dynamism m_dynamism;
+ const RatioChange m_ratio_change;
+ const int m_debug_level;
+ const double m_initial_rate;
+ const int m_channels;
+
+ struct params {
+ double ratio;
+ int numerator;
+ int denominator;
+ double effective;
+ double peak_to_zero;
+ double scale;
+ params() : ratio(1.0), numerator(1), denominator(1),
+ effective(1.0), peak_to_zero(0), scale(1.0) { }
+ };
+
+ struct phase_rec {
+ int next_phase;
+ int length;
+ int start_index;
+ int drop;
+ phase_rec() : next_phase(0), length(0), start_index(0), drop(0) { }
+ };
+
+ typedef std::vector<float, breakfastquay::StlAllocator<float> > floatbuf;
+
+ struct state {
+ params parameters;
+ int initial_phase;
+ int current_phase;
+ int current_channel;
+ int filter_length;
+ std::vector<phase_rec> phase_info;
+ floatbuf phase_sorted_filter;
+ floatbuf buffer;
+ int left;
+ int centre;
+ int fill;
+ state() : initial_phase(0), current_phase(0), current_channel(0),
+ filter_length(0), left(0), centre(0), fill(0) { }
+ };
+
+ state m_state_a;
+ state m_state_b;
+
+ state *m_s; // points at either m_state_a or m_state_b
+ state *m_fade; // whichever one m_s does not point to
+
+ int m_fade_count;
+
+ std::vector<double> m_prototype;
+ int m_proto_length;
+ bool m_initialised;
+
+ int gcd(int a, int b) const;
+ double bessel0(double x) const;
+ std::vector<double> kaiser(double beta, int len) const;
+ void kaiser_params(double attenuation, double transition,
+ double &beta, int &len) const;
+ std::vector<double> kaiser_for(double attenuation, double transition,
+ int minlen, int maxlen) const;
+ void sinc_multiply(double peak_to_zero, std::vector<double> &buf) const;
+
+ params fill_params(double ratio, int num, int denom) const;
+ params pick_params(double ratio) const;
+
+ std::vector<double> make_filter(int filter_length,
+ double peak_to_zero) const;
+
+ void phase_data_for(std::vector<phase_rec> &target_phase_data,
+ floatbuf &target_phase_sorted_filter,
+ int filter_length,
+ const std::vector<double> *filter,
+ int initial_phase,
+ int input_spacing,
+ int output_spacing) const;
+
+ void state_for_ratio(state &target_state,
+ double ratio,
+ const state &BQ_R__ prev_state) const;
+
+ double reconstruct_one(state *s) const;
+
+ BQResampler &operator=(const BQResampler &); // not provided
+};
+
+}
+
+#endif
M src/Resampler.cpp +212 -16
@@ 69,15 69,21 @@
#include "../speex/speex_resampler.h"
#endif
+#ifdef USE_BQRESAMPLER
+#include "BQResampler.h"
+#endif
+
#ifndef HAVE_IPP
#ifndef HAVE_LIBSAMPLERATE
#ifndef HAVE_LIBRESAMPLE
#ifndef USE_SPEEX
+#ifndef USE_BQRESAMPLER
#error No resampler implementation selected!
#endif
#endif
#endif
#endif
+#endif
using namespace std;
@@ 103,6 109,7 @@ public:
bool final) = 0;
virtual int getChannelCount() const = 0;
+ virtual double getEffectiveRatio(double ratio) const = 0;
virtual void reset() = 0;
};
@@ 114,7 121,8 @@ namespace Resamplers {
class D_IPP : public Resampler::Impl
{
public:
- D_IPP(Resampler::Quality quality, int channels, double initialSampleRate,
+ D_IPP(Resampler::Quality quality, Resampler::RatioChange,
+ int channels, double initialSampleRate,
int maxBufferSize, int debugLevel);
~D_IPP();
@@ 133,6 141,7 @@ public:
bool final = false);
int getChannelCount() const { return m_channels; }
+ double getEffectiveRatio(double ratio) const { return ratio; }
void reset();
@@ 159,6 168,7 @@ protected:
};
D_IPP::D_IPP(Resampler::Quality /* quality */,
+ Resampler::RatioChange /* ratioChange */,
int channels, double initialSampleRate,
int maxBufferSize, int debugLevel) :
m_state(0),
@@ 167,7 177,7 @@ D_IPP::D_IPP(Resampler::Quality /* quali
m_debugLevel(debugLevel)
{
if (m_debugLevel > 0) {
- cerr << "Resampler::Resampler: using IPP implementation" << endl;
+ cerr << "Resampler::Resampler: using implementation: IPP" << endl;
}
m_window = 32;
@@ 543,7 553,8 @@ D_IPP::reset()
class D_SRC : public Resampler::Impl
{
public:
- D_SRC(Resampler::Quality quality, int channels, double initialSampleRate,
+ D_SRC(Resampler::Quality quality, Resampler::RatioChange ratioChange,
+ int channels, double initialSampleRate,
int maxBufferSize, int m_debugLevel);
~D_SRC();
@@ 562,6 573,7 @@ public:
bool final = false);
int getChannelCount() const { return m_channels; }
+ double getEffectiveRatio(double ratio) const { return ratio; }
void reset();
@@ 574,11 586,12 @@ protected:
int m_ioutsize;
double m_prevRatio;
bool m_ratioUnset;
+ bool m_smoothRatios;
int m_debugLevel;
};
-D_SRC::D_SRC(Resampler::Quality quality, int channels, double,
- int maxBufferSize, int debugLevel) :
+D_SRC::D_SRC(Resampler::Quality quality, Resampler::RatioChange ratioChange,
+ int channels, double, int maxBufferSize, int debugLevel) :
m_src(0),
m_iin(0),
m_iout(0),
@@ 587,10 600,11 @@ D_SRC::D_SRC(Resampler::Quality quality,
m_ioutsize(0),
m_prevRatio(1.0),
m_ratioUnset(true),
+ m_smoothRatios(ratioChange == Resampler::SmoothRatioChange),
m_debugLevel(debugLevel)
{
if (m_debugLevel > 0) {
- cerr << "Resampler::Resampler: using libsamplerate implementation"
+ cerr << "Resampler::Resampler: using implementation: libsamplerate"
<< endl;
}
@@ 692,7 706,7 @@ D_SRC::resampleInterleaved(float *const
outcount = int(ceil(incount * ratio) + 5);
}
- if (m_ratioUnset) {
+ if (m_ratioUnset || !m_smoothRatios) {
// The first time we set a ratio, we want to do it directly
src_set_ratio(m_src, ratio);
@@ 764,7 778,8 @@ D_SRC::reset()
class D_Resample : public Resampler::Impl
{
public:
- D_Resample(Resampler::Quality quality, int channels, double initialSampleRate,
+ D_Resample(Resampler::Quality quality, Resampler::RatioChange,
+ int channels, double initialSampleRate,
int maxBufferSize, int m_debugLevel);
~D_Resample();
@@ 783,6 798,7 @@ public:
bool final);
int getChannelCount() const { return m_channels; }
+ double getEffectiveRatio(double ratio) const { return ratio; }
void reset();
@@ 808,7 824,7 @@ D_Resample::D_Resample(Resampler::Qualit
m_debugLevel(debugLevel)
{
if (m_debugLevel > 0) {
- cerr << "Resampler::Resampler: using libresample implementation"
+ cerr << "Resampler::Resampler: using implementation: libresample"
<< endl;
}
@@ 946,12 962,167 @@ D_Resample::reset()
#endif /* HAVE_LIBRESAMPLE */
+#ifdef USE_BQRESAMPLER
+
+class D_BQResampler : public Resampler::Impl
+{
+public:
+ D_BQResampler(Resampler::Parameters params, int channels);
+ ~D_BQResampler();
+
+ int resample(float *const BQ_R__ *const BQ_R__ out,
+ int outcount,
+ const float *const BQ_R__ *const BQ_R__ in,
+ int incount,
+ double ratio,
+ bool final);
+
+ int resampleInterleaved(float *const BQ_R__ out,
+ int outcount,
+ const float *const BQ_R__ in,
+ int incount,
+ double ratio,
+ bool final = false);
+
+ int getChannelCount() const {
+ return m_channels;
+ }
+
+ double getEffectiveRatio(double ratio) const {
+ return m_resampler->getEffectiveRatio(ratio);
+ }
+
+ void reset();
+
+protected:
+ BQResampler *m_resampler;
+ float *m_iin;
+ float *m_iout;
+ int m_channels;
+ int m_iinsize;
+ int m_ioutsize;
+ int m_debugLevel;
+};
+
+D_BQResampler::D_BQResampler(Resampler::Parameters params, int channels) :
+ m_resampler(0),
+ m_iin(0),
+ m_iout(0),
+ m_channels(channels),
+ m_iinsize(0),
+ m_ioutsize(0),
+ m_debugLevel(params.debugLevel)
+{
+ if (m_debugLevel > 0) {
+ cerr << "Resampler::Resampler: using implementation: BQResampler" << endl;
+ }
+
+ BQResampler::Parameters rparams;
+ switch (params.quality) {
+ case Resampler::Best:
+ rparams.quality = BQResampler::Best;
+ break;
+ case Resampler::FastestTolerable:
+ rparams.quality = BQResampler::FastestTolerable;
+ break;
+ case Resampler::Fastest:
+ rparams.quality = BQResampler::Fastest;
+ break;
+ }
+ switch (params.dynamism) {
+ case Resampler::RatioOftenChanging:
+ rparams.dynamism = BQResampler::RatioOftenChanging;
+ break;
+ case Resampler::RatioMostlyFixed:
+ rparams.dynamism = BQResampler::RatioMostlyFixed;
+ break;
+ }
+ switch (params.ratioChange) {
+ case Resampler::SmoothRatioChange:
+ rparams.ratioChange = BQResampler::SmoothRatioChange;
+ break;
+ case Resampler::SuddenRatioChange:
+ rparams.ratioChange = BQResampler::SuddenRatioChange;
+ break;
+ }
+ rparams.referenceSampleRate = params.initialSampleRate;
+ rparams.debugLevel = params.debugLevel;
+
+ m_resampler = new BQResampler(rparams, m_channels);
+
+ if (params.maxBufferSize > 0 && m_channels > 1) {
+ m_iinsize = params.maxBufferSize * m_channels;
+ m_ioutsize = params.maxBufferSize * m_channels * 2;
+ m_iin = allocate<float>(m_iinsize);
+ m_iout = allocate<float>(m_ioutsize);
+ }
+}
+
+D_BQResampler::~D_BQResampler()
+{
+ delete m_resampler;
+ deallocate(m_iin);
+ deallocate(m_iout);
+}
+
+int
+D_BQResampler::resample(float *const BQ_R__ *const BQ_R__ out,
+ int outcount,
+ const float *const BQ_R__ *const BQ_R__ in,
+ int incount,
+ double ratio,
+ bool final)
+{
+ if (m_channels == 1) {
+ return resampleInterleaved(*out, outcount, *in, incount, ratio, final);
+ }
+
+ if (incount * m_channels > m_iinsize) {
+ m_iin = reallocate<float>(m_iin, m_iinsize, incount * m_channels);
+ m_iinsize = incount * m_channels;
+ }
+ if (outcount * m_channels > m_ioutsize) {
+ m_iout = reallocate<float>(m_iout, m_ioutsize, outcount * m_channels);
+ m_ioutsize = outcount * m_channels;
+ }
+
+ v_interleave(m_iin, in, m_channels, incount);
+
+ int n = resampleInterleaved(m_iout, outcount, m_iin, incount, ratio, final);
+
+ v_deinterleave(out, m_iout, m_channels, n);
+
+ return n;
+}
+
+int
+D_BQResampler::resampleInterleaved(float *const BQ_R__ out,
+ int outcount,
+ const float *const BQ_R__ in,
+ int incount,
+ double ratio,
+ bool final)
+{
+ return m_resampler->resampleInterleaved(out, outcount,
+ in, incount,
+ ratio, final);
+}
+
+void
+D_BQResampler::reset()
+{
+ m_resampler->reset();
+}
+
+#endif /* USE_BQRESAMPLER */
+
#ifdef USE_SPEEX
class D_Speex : public Resampler::Impl
{
public:
- D_Speex(Resampler::Quality quality, int channels, double initialSampleRate,
+ D_Speex(Resampler::Quality quality, Resampler::RatioChange,
+ int channels, double initialSampleRate,
int maxBufferSize, int debugLevel);
~D_Speex();
@@ 970,6 1141,7 @@ public:
bool final = false);
int getChannelCount() const { return m_channels; }
+ double getEffectiveRatio(double ratio) const { return ratio; }
void reset();
@@ 991,7 1163,7 @@ protected:
double ratio, bool final);
};
-D_Speex::D_Speex(Resampler::Quality quality,
+D_Speex::D_Speex(Resampler::Quality quality, Resampler::RatioChange,
int channels, double initialSampleRate,
int maxBufferSize, int debugLevel) :
m_resampler(0),
@@ 1009,7 1181,7 @@ D_Speex::D_Speex(Resampler::Quality qual
quality == Resampler::Fastest ? 0 : 4);
if (m_debugLevel > 0) {
- cerr << "Resampler::Resampler: using Speex implementation with q = "
+ cerr << "Resampler::Resampler: using implementation: Speex with q = "
<< q << endl;
}
@@ 1245,6 1417,9 @@ Resampler::Resampler(Resampler::Paramete
#ifdef HAVE_LIBRESAMPLE
m_method = 3;
#endif
+#ifdef USE_BQRESAMPLER
+ m_method = 4;
+#endif
#ifdef HAVE_LIBSAMPLERATE
m_method = 1;
#endif
@@ 1260,6 1435,9 @@ Resampler::Resampler(Resampler::Paramete
#ifdef USE_SPEEX
m_method = 2;
#endif
+#ifdef USE_BQRESAMPLER
+ m_method = 4;
+#endif
#ifdef HAVE_LIBSAMPLERATE
m_method = 1;
#endif
@@ 1275,6 1453,9 @@ Resampler::Resampler(Resampler::Paramete
#ifdef USE_SPEEX
m_method = 2;
#endif
+#ifdef USE_BQRESAMPLER
+ m_method = 4;
+#endif
#ifdef HAVE_LIBSAMPLERATE
m_method = 1;
#endif
@@ 1290,7 1471,7 @@ Resampler::Resampler(Resampler::Paramete
case 0:
#ifdef HAVE_IPP
d = new Resamplers::D_IPP
- (params.quality,
+ (params.quality, params.ratioChange,
channels,
params.initialSampleRate, params.maxBufferSize, params.debugLevel);
#else
@@ 1302,7 1483,7 @@ Resampler::Resampler(Resampler::Paramete
case 1:
#ifdef HAVE_LIBSAMPLERATE
d = new Resamplers::D_SRC
- (params.quality,
+ (params.quality, params.ratioChange,
channels,
params.initialSampleRate, params.maxBufferSize, params.debugLevel);
#else
@@ 1314,7 1495,7 @@ Resampler::Resampler(Resampler::Paramete
case 2:
#ifdef USE_SPEEX
d = new Resamplers::D_Speex
- (params.quality,
+ (params.quality, params.ratioChange,
channels,
params.initialSampleRate, params.maxBufferSize, params.debugLevel);
#else
@@ 1326,7 1507,7 @@ Resampler::Resampler(Resampler::Paramete
case 3:
#ifdef HAVE_LIBRESAMPLE
d = new Resamplers::D_Resample
- (params.quality,
+ (params.quality, params.ratioChange,
channels,
params.initialSampleRate, params.maxBufferSize, params.debugLevel);
#else
@@ 1334,6 1515,15 @@ Resampler::Resampler(Resampler::Paramete
abort();
#endif
break;
+
+ case 4:
+#ifdef USE_BQRESAMPLER
+ d = new Resamplers::D_BQResampler(params, channels);
+#else
+ cerr << "Resampler::Resampler: No implementation available!" << endl;
+ abort();
+#endif
+ break;
}
if (!d) {
@@ 1376,6 1566,12 @@ Resampler::getChannelCount() const
return d->getChannelCount();
}
+double
+Resampler::getEffectiveRatio(double ratio) const
+{
+ return d->getEffectiveRatio(ratio);
+}
+
void
Resampler::reset()
{
A => test/e2e/e2e.cpp +149 -0
@@ 0,0 1,149 @@
+
+// Not a standalone source file
+
+void usage()
+{
+ cerr << "This is a test program for bqresample. Please do not try to use it in earnest." << endl;
+ cerr << "Usage: " << programName << " [-v] [-c <converter>] <infile> <outfile>" << endl;
+ cerr << "where <converter> may be 0, 1, or 2, for best, medium, or fastest respectively." << endl;
+ cerr << "The default converter is 0, best." << endl;
+ cerr << "Supply -v for verbose output." << endl;
+ exit(2);
+}
+
+int main(int argc, char **argv)
+{
+ int quality = 0;
+ int arg;
+ bool verbose = false;
+
+ for (arg = 1; arg + 2 < argc; ++arg) {
+ if (!strcmp(argv[arg], "-c")) {
+ char *e = argv[arg+1];
+ quality = strtol(argv[arg+1], &e, 10);
+ if (*e || (quality < 0) || (quality > 2)) {
+ cerr << "error: invalid converter \""
+ << argv[arg+1] << "\" (must be 0, 1, 2)" << endl;
+ usage();
+ }
+ ++arg;
+ continue;
+ } else if (!strcmp(argv[arg], "-v")) {
+ verbose = true;
+ } else {
+ cerr << "error: unexpected option \"" << argv[arg] << "\"" << endl;
+ usage();
+ }
+ }
+
+ if (arg + 2 != argc) {
+ usage();
+ }
+
+ string infilename = argv[arg];
+ string outfilename = argv[arg+1];
+
+ SF_INFO info_in;
+ SNDFILE *file_in = sf_open(infilename.c_str(), SFM_READ, &info_in);
+ if (!file_in) {
+ cerr << "failed to open " << infilename << endl;
+ return 1;
+ }
+
+ int channels = info_in.channels;
+
+ if (verbose) {
+ cerr << "input rate = " << info_in.samplerate << endl;
+ }
+
+ double ratio = initialRatio;
+
+ SF_INFO info_out;
+ memset(&info_out, 0, sizeof(SF_INFO));
+ info_out.channels = channels;
+ info_out.format = info_in.format;
+ info_out.samplerate = info_in.samplerate;
+ SNDFILE *file_out = sf_open(outfilename.c_str(), SFM_WRITE, &info_out);
+ if (!file_out) {
+ cerr << "failed to open " << outfilename << endl;
+ return 1;
+ }
+
+ int ibs = 1024;
+ int obs = ceil(ibs * ratio * 10);
+ float *ibuf = new float[ibs * channels];
+ float *obuf = new float[obs * channels];
+
+ breakfastquay::Resampler::Parameters parameters;
+ switch (quality) {
+ case 0:
+ parameters.quality = breakfastquay::Resampler::Best;
+ if (verbose) {
+ cerr << "quality = best" << endl;
+ }
+ break;
+ case 1:
+ parameters.quality = breakfastquay::Resampler::FastestTolerable;
+ if (verbose) {
+ cerr << "quality = middling" << endl;
+ }
+ break;
+ case 2:
+ parameters.quality = breakfastquay::Resampler::Fastest;
+ if (verbose) {
+ cerr << "quality = worst" << endl;
+ }
+ break;
+ }
+
+ if (isRatioChanging()) {
+ parameters.dynamism = breakfastquay::Resampler::RatioOftenChanging;
+ parameters.ratioChange = breakfastquay::Resampler::SmoothRatioChange;
+ } else {
+ parameters.dynamism = breakfastquay::Resampler::RatioMostlyFixed;
+ parameters.ratioChange = breakfastquay::Resampler::SuddenRatioChange;
+ }
+
+ parameters.initialSampleRate = info_in.samplerate;
+ parameters.debugLevel = (verbose ? 1 : 0);
+ breakfastquay::Resampler resampler(parameters, channels);
+
+ int n = 0;
+ while (true) {
+ int count = sf_readf_float(file_in, ibuf, ibs);
+ if (verbose) {
+ cerr << ".";
+ }
+ if (count < 0) {
+ cerr << "error: count = " << count << endl;
+ break;
+ }
+
+ bool final = (count < ibs);
+ int got = resampler.resampleInterleaved
+ (obuf, obs, ibuf, count, ratio, final);
+
+ if (got == 0 && final) {
+ break;
+ } else {
+ for (int i = 0; i < got; ++i) {
+ if (obuf[i] < -1.0) obuf[i] = -1.0;
+ if (obuf[i] > 1.0) obuf[i] = 1.0;
+ }
+ sf_writef_float(file_out, obuf, got);
+ }
+
+ ratio = nextRatio(ratio);
+ ++n;
+ }
+
+ if (verbose) {
+ cerr << endl;
+ }
+
+ sf_close(file_in);
+ sf_close(file_out);
+
+ delete[] ibuf;
+ delete[] obuf;
+}
A => test/e2e/halve.cpp +21 -0
@@ 0,0 1,21 @@
+
+#include "bqresample/Resampler.h"
+
+#include <sndfile.h>
+#include <cstring>
+#include <cstdlib>
+#include <cmath>
+#include <iostream>
+
+using namespace std;
+
+static const string programName = "halve";
+static const double initialRatio = 0.5;
+static double nextRatio(double ratio) {
+ return ratio;
+}
+static bool isRatioChanging() {
+ return false;
+}
+
+#include "e2e.cpp"
A => test/e2e/oversample.cpp +21 -0
@@ 0,0 1,21 @@
+
+#include "bqresample/Resampler.h"
+
+#include <sndfile.h>
+#include <cstring>
+#include <cstdlib>
+#include <cmath>
+#include <iostream>
+
+using namespace std;
+
+static const string programName = "oversample";
+static const double initialRatio = 16.0;
+static double nextRatio(double ratio) {
+ return ratio;
+}
+static bool isRatioChanging() {
+ return false;
+}
+
+#include "e2e.cpp"
A => test/e2e/random.cpp +21 -0
@@ 0,0 1,21 @@
+
+#include "bqresample/Resampler.h"
+
+#include <sndfile.h>
+#include <cstring>
+#include <cstdlib>
+#include <cmath>
+#include <iostream>
+
+using namespace std;
+
+static const string programName = "random";
+static const double initialRatio = 0.5;
+static double nextRatio(double) {
+ return drand48() * 2.5 + 0.1;
+}
+static bool isRatioChanging() {
+ return true;
+}
+
+#include "e2e.cpp"
A => test/e2e/sweep-log.wav +0 -0
A => test/e2e/undulating.cpp +25 -0
@@ 0,0 1,25 @@
+
+#include "bqresample/Resampler.h"
+
+#include <sndfile.h>
+#include <cstring>
+#include <cstdlib>
+#include <cmath>
+#include <iostream>
+
+using namespace std;
+
+static const string programName = "undulating";
+static const double initialRatio = 0.5;
+static double nextRatio(double ratio) {
+ static double factor = 1.01;
+ ratio *= factor;
+ if (ratio > 3.0) factor = 0.99;
+ else if (ratio < 0.3) factor = 1.01;
+ return ratio;
+}
+static bool isRatioChanging() {
+ return true;
+}
+
+#include "e2e.cpp"