7cbc89dca2b7 — Chris Cannam 10 days ago
Merge from branch single
M .hgignore +1 -0
@@ 24,4 24,5 @@ Release/
 Debug/
 build
 build_*
+build-*
 UpgradeLog*

          
M Doxyfile +1 -1
@@ 31,7 31,7 @@ PROJECT_NAME           = "Rubber Band Li
 # This could be handy for archiving the generated documentation or 
 # if some version control system is used.
 
-PROJECT_NUMBER         = 1.9.2
+PROJECT_NUMBER         = 2.0.0
 
 # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) 
 # base path where the generated documentation will be put. 

          
M README.md +49 -31
@@ 1,5 1,5 @@ 
 
-# Rubber Band
+# Rubber Band Library
 
 An audio time-stretching and pitch-shifting library and utility program.
 

          
@@ 29,12 29,12 @@ License (GPL). You can redistribute it a
 terms of the GPL; either version 2 of the License, or (at your option)
 any later version. See the file COPYING for more information.
 
-If you wish to distribute code using the Rubber Band Library under
-terms other than those of the GNU General Public License, you must
-obtain a commercial licence from us before doing so. In particular,
-you may not legally distribute through any Apple App Store unless you
-have a commercial licence.  See https://breakfastquay.com/rubberband/
-for licence terms.
+If you wish to distribute code using Rubber Band Library under terms
+other than those of the GNU General Public License, you must obtain a
+commercial licence from us before doing so. In particular, you may not
+legally distribute through any Apple App Store unless you have a
+commercial licence.  See https://breakfastquay.com/rubberband/ for
+licence terms.
 
 If you have obtained a valid commercial licence, your licence
 supersedes this README and the enclosed COPYING file and you may

          
@@ 59,7 59,6 @@ our knowledge. See also the end of this 
  * Intel IPP - Proprietary; licence needed for redistribution
  * KissFFT - BSD-like
  * libsamplerate - BSD-like from version 0.1.9 onwards
- * libresample - LGPL
  * Speex - BSD-like
  * Pommier math functions - BSD-like
  

          
@@ 68,7 67,7 @@ our knowledge. See also the end of this 
 
 1. Code components
 2. Using the Rubber Band command-line tool
-3. Using the Rubber Band Library
+3. Using Rubber Band Library
 4. Compiling Rubber Band
     a. Building on Linux
     b. Building on macOS

          
@@ 91,7 90,7 @@ Rubber Band consists of:
    and FFT code; see section 3a below for details.
 
  * The Rubber Band command-line tool.  This is in main/main.cpp.
-   This program uses the Rubber Band Library and also requires libsndfile
+   This program uses Rubber Band Library and also requires libsndfile
    (http://www.mega-nerd.com/libsndfile/, licensed under the GNU Lesser
    General Public License) for audio file loading.
 

          
@@ 127,12 126,12 @@ In particular, different types of music 
 "crispness" options (-c flag with a numerical argument from 0 to 6).
 
 
-## 3. Using the Rubber Band Library
+## 3. Using Rubber Band Library
 
-The Rubber Band Library has a public API that consists of one C++
-class, called RubberBandStretcher in the RubberBand namespace.  You
-should `#include <rubberband/RubberBandStretcher.h>` to use this
-class.  There is extensive documentation in the class header.
+Rubber Band has a public API that consists of one C++ class, called
+`RubberBandStretcher` in the `RubberBand` namespace.  You should
+`#include <rubberband/RubberBandStretcher.h>` to use this class.
+There is extensive documentation in the class header.
 
 A header with C language bindings is also provided in
 `<rubberband/rubberband-c.h>`.  This is a wrapper around the C++

          
@@ 158,18 157,26 @@ for modification and redistribution) unl
 acquired a commercial licence from the author.
 
 
-## 4. Compiling the Rubber Band Library
+## 4. Compiling Rubber Band Library
 
-The primary supported build system for the Rubber Band Library on all
-platforms is Meson (https://mesonbuild.com). The Meson build system
-can be used to build all targets (static and dynamic library,
-command-line utility, and plugins) and to cross-compile.
+The primary supported build system for Rubber Band on all platforms is
+Meson (https://mesonbuild.com). The Meson build system can be used to
+build all targets (static and dynamic library, command-line utility,
+and plugins) and to cross-compile.
 
-If you only need a static library and don't wish to use Meson, some
+☞ If you only need a static library and don't wish to use Meson, some
 alternative build files (Makefiles and Visual C++ projects) are
 included in the `otherbuilds` directory. See the platform-specific
 build sections below for more details.
 
+☞ If you want to include Rubber Band in a C++ project and would prefer
+not to compile a separate library, there is a single `.cpp` file at
+`single/RubberBandSingle.cpp` which can be added to your project
+as-is.  It produces a single compilation-unit build with the built-in
+FFT and resampler implementations with no further library
+dependencies. See the comments at the top of that file for more
+information.
+
 To build with Meson, ensure Meson and Ninja are installed and run:
 
 ```

          
@@ 193,10 200,10 @@ Meson:
 The options are documented in the library- and platform-specific
 sections below.
 
-The Rubber Band Library is written entirely in C++ to the C++98
-standard. It is unlikely to make any difference (performance or
-otherwise) which C++ standard your compiler uses - as long as it's no
-older than C++98!
+Rubber Band Library is written entirely in C++ and requires a C++11
+compiler. It is unlikely to make any difference (performance or
+otherwise) which C++ standard you compile with, as long as it's no
+older than C++11.
 
 If you are building this software using either of the Speex or KissFFT
 library options, please be sure to review the terms for those

          
@@ 268,8 275,8 @@ See "FFT and resampler selection" below 
 
 Note that you cannot legally distribute applications using Rubber Band
 in the Mac App Store, unless you have first obtained a commercial
-licence for the Rubber Band Library.  GPL code is not permitted in the
-app store.  See https://breakfastquay.com/technology/license.html for
+licence for Rubber Band Library.  GPL code is not permitted in the app
+store.  See https://breakfastquay.com/technology/license.html for
 commercial terms.
 
 

          
@@ 295,8 302,8 @@ See "FFT and resampler selection" below 
 
 Note that you cannot legally distribute applications using Rubber Band
 in the iOS App Store, unless you have a first obtained a commercial
-licence for the Rubber Band Library. GPL code is not permitted in the
-app store.  See https://breakfastquay.com/technology/license.html for
+licence for Rubber Band Library. GPL code is not permitted in the app
+store.  See https://breakfastquay.com/technology/license.html for
 commercial terms.
 
 

          
@@ 409,10 416,21 @@ Library     Build option    CPP define  
 ----        ------------    ----------     -----
 
 libsamplerate               -DHAVE_LIBSAMPLERATE
-            -Dresampler=libsamplerate      Best choice in most cases.
+            -Dresampler=libsamplerate      Default when found.
+                                           Good choice in most cases.
+
+Built-in    -Dfft=builtin   -DUSE_BQRESAMPLER
+                                           Default when libsamplerate not found.
+                                           Can be distributed with either
+                                           the Rubber Band GPL or
+                                           commercial licence. Intended to
+                                           give best quality for time-varying
+                                           pitch shifts in real-time mode.
+                                           Newer than, and not as well-tested
+                                           as, libsamplerate.
 
 Speex                       -DUSE_SPEEX
-            -Dresampler=speex              Bundled, can be distributed with
+            -Dresampler=speex              Can be distributed with
 	    				   either the Rubber Band GPL or
 					   commercial licence.
 ```

          
M ladspa/RubberBandPitchShifter.cpp +72 -82
@@ 44,7 44,7 @@ RubberBandPitchShifter::portNamesMono[Po
     "Octaves",
     "Crispness",
     "Formant Preserving",
-    "Faster",
+    "Wet-Dry Mix",
     "Input",
     "Output"
 };

          
@@ 58,7 58,7 @@ RubberBandPitchShifter::portNamesStereo[
     "Octaves",
     "Crispness",
     "Formant Preserving",
-    "Faster",
+    "Wet-Dry Mix",
     "Input L",
     "Output L",
     "Input R",

          
@@ 112,7 112,7 @@ RubberBandPitchShifter::hintsMono[PortCo
       LADSPA_HINT_BOUNDED_BELOW |
       LADSPA_HINT_BOUNDED_ABOVE |
       LADSPA_HINT_INTEGER,
-      -3.0, 3.0 },
+      -2.0, 2.0 },
     { LADSPA_HINT_DEFAULT_MAXIMUM |     // crispness
       LADSPA_HINT_BOUNDED_BELOW |
       LADSPA_HINT_BOUNDED_ABOVE |

          
@@ 123,10 123,9 @@ RubberBandPitchShifter::hintsMono[PortCo
       LADSPA_HINT_BOUNDED_ABOVE |
       LADSPA_HINT_TOGGLED,
        0.0, 1.0 },
-    { LADSPA_HINT_DEFAULT_0 |           // fast
+    { LADSPA_HINT_DEFAULT_0 |           // wet-dry mix
       LADSPA_HINT_BOUNDED_BELOW |
-      LADSPA_HINT_BOUNDED_ABOVE |
-      LADSPA_HINT_TOGGLED,
+      LADSPA_HINT_BOUNDED_ABOVE,
        0.0, 1.0 },
     { 0, 0, 0 },
     { 0, 0, 0 }

          
@@ 149,7 148,7 @@ RubberBandPitchShifter::hintsStereo[Port
       LADSPA_HINT_BOUNDED_BELOW |
       LADSPA_HINT_BOUNDED_ABOVE |
       LADSPA_HINT_INTEGER,
-      -3.0, 3.0 },
+      -2.0, 2.0 },
     { LADSPA_HINT_DEFAULT_MAXIMUM |     // crispness
       LADSPA_HINT_BOUNDED_BELOW |
       LADSPA_HINT_BOUNDED_ABOVE |

          
@@ 160,10 159,9 @@ RubberBandPitchShifter::hintsStereo[Port
       LADSPA_HINT_BOUNDED_ABOVE |
       LADSPA_HINT_TOGGLED,
        0.0, 1.0 },
-    { LADSPA_HINT_DEFAULT_0 |           // fast
+    { LADSPA_HINT_DEFAULT_0 |           // wet-dry mix
       LADSPA_HINT_BOUNDED_BELOW |
-      LADSPA_HINT_BOUNDED_ABOVE |
-      LADSPA_HINT_TOGGLED,
+      LADSPA_HINT_BOUNDED_ABOVE,
        0.0, 1.0 },
     { 0, 0, 0 },
     { 0, 0, 0 },

          
@@ 237,14 235,14 @@ RubberBandPitchShifter::RubberBandPitchS
     m_octaves(0),
     m_crispness(0),
     m_formant(0),
-    m_fast(0),
+    m_wetDry(0),
     m_ratio(1.0),
     m_prevRatio(1.0),
     m_currentCrispness(-1),
     m_currentFormant(false),
-    m_currentFast(false),
     m_blockSize(1024),
-    m_reserve(1024),
+    m_reserve(8192),
+    m_bufsize(0),
     m_minfill(0),
     m_stretcher(new RubberBandStretcher
                 (sampleRate, channels,

          
@@ 257,19 255,23 @@ RubberBandPitchShifter::RubberBandPitchS
     m_output = new float *[m_channels];
 
     m_outputBuffer = new RingBuffer<float> *[m_channels];
+    m_delayMixBuffer = new RingBuffer<float> *[m_channels];
     m_scratch = new float *[m_channels];
     
+    m_bufsize = m_blockSize + m_reserve + 8192;
+
     for (size_t c = 0; c < m_channels; ++c) {
 
         m_input[c] = 0;
         m_output[c] = 0;
 
-        int bufsize = m_blockSize + m_reserve + 8192;
+        m_outputBuffer[c] = new RingBuffer<float>(m_bufsize);
+        m_delayMixBuffer[c] = new RingBuffer<float>(m_bufsize);
 
-        m_outputBuffer[c] = new RingBuffer<float>(bufsize);
-
-        m_scratch[c] = new float[bufsize];
-        for (int i = 0; i < bufsize; ++i) m_scratch[c][i] = 0.f;
+        m_scratch[c] = new float[m_bufsize];
+        for (size_t i = 0; i < m_bufsize; ++i) {
+            m_scratch[c][i] = 0.f;
+        }
     }
 
     activateImpl();

          
@@ 280,9 282,11 @@ RubberBandPitchShifter::~RubberBandPitch
     delete m_stretcher;
     for (size_t c = 0; c < m_channels; ++c) {
         delete m_outputBuffer[c];
+        delete m_delayMixBuffer[c];
         delete[] m_scratch[c];
     }
     delete[] m_outputBuffer;
+    delete[] m_delayMixBuffer;
     delete[] m_scratch;
     delete[] m_output;
     delete[] m_input;

          
@@ 312,7 316,7 @@ RubberBandPitchShifter::connectPort(LADS
 	&shifter->m_octaves,
         &shifter->m_crispness,
 	&shifter->m_formant,
-	&shifter->m_fast,
+	&shifter->m_wetDry,
     	&shifter->m_input[0],
 	&shifter->m_output[0],
 	&shifter->m_input[1],

          
@@ 328,11 332,16 @@ RubberBandPitchShifter::connectPort(LADS
     *ports[port] = (float *)location;
 
     if (shifter->m_latency) {
-        *(shifter->m_latency) =
-            float(shifter->m_stretcher->getLatency() + shifter->m_reserve);
+        *(shifter->m_latency) = shifter->getLatency();
     }
 }
 
+int
+RubberBandPitchShifter::getLatency() const
+{
+    return m_reserve;
+}
+
 void
 RubberBandPitchShifter::activate(LADSPA_Handle handle)
 {

          
@@ 350,20 359,22 @@ RubberBandPitchShifter::activateImpl()
 
     for (size_t c = 0; c < m_channels; ++c) {
         m_outputBuffer[c]->reset();
-        m_outputBuffer[c]->zero(m_reserve);
+    }
+
+    for (size_t c = 0; c < m_channels; ++c) {
+        m_delayMixBuffer[c]->reset();
+        m_delayMixBuffer[c]->zero(getLatency());
+    }
+    
+    for (size_t c = 0; c < m_channels; ++c) {
+        for (size_t i = 0; i < m_bufsize; ++i) {
+            m_scratch[c][i] = 0.f;
+        }
     }
 
     m_minfill = 0;
 
-    // prime stretcher
-//    for (int i = 0; i < 8; ++i) {
-//        int reqd = m_stretcher->getSamplesRequired();
-//        m_stretcher->process(m_scratch, reqd, false);
-//        int avail = m_stretcher->available();
-//        if (avail > 0) {
-//            m_stretcher->retrieve(m_scratch, avail);
-//        }
-//    }
+    m_stretcher->process(m_scratch, m_reserve, false);
 }
 
 void

          
@@ 432,23 443,6 @@ RubberBandPitchShifter::updateFormant()
 }
 
 void
-RubberBandPitchShifter::updateFast()
-{
-    if (!m_fast) return;
-
-    bool f = (*m_fast > 0.5f);
-    if (f == m_currentFast) return;
-    
-    RubberBandStretcher *s = m_stretcher;
-    
-    s->setPitchOption(f ?
-                      RubberBandStretcher::OptionPitchHighSpeed :
-                      RubberBandStretcher::OptionPitchHighConsistency);
-
-    m_currentFast = f;
-}
-
-void
 RubberBandPitchShifter::runImpl(unsigned long insamples)
 {
     unsigned long offset = 0;

          
@@ 466,15 460,29 @@ RubberBandPitchShifter::runImpl(unsigned
 
         offset += block;
     }
+
+    if (m_wetDry) {
+        for (size_t c = 0; c < m_channels; ++c) {
+            m_delayMixBuffer[c]->write(m_input[c], insamples);
+        }
+        float mix = *m_wetDry;
+        for (size_t c = 0; c < m_channels; ++c) {
+            if (mix > 0.0) {
+                for (unsigned long i = 0; i < insamples; ++i) {
+                    float dry = m_delayMixBuffer[c]->readOne();
+                    m_output[c][i] *= (1.0 - mix);
+                    m_output[c][i] += dry * mix;
+                }
+            } else {
+                m_delayMixBuffer[c]->skip(insamples);
+            }
+        }
+    }
 }
 
 void
 RubberBandPitchShifter::runImpl(unsigned long insamples, unsigned long offset)
 {
-//    cerr << "RubberBandPitchShifter::runImpl(" << insamples << ")" << endl;
-
-//    static int incount = 0, outcount = 0;
-
     updateRatio();
     if (m_ratio != m_prevRatio) {
         m_stretcher->setPitchScale(m_ratio);

          
@@ 482,13 490,11 @@ RubberBandPitchShifter::runImpl(unsigned
     }
 
     if (m_latency) {
-        *m_latency = float(m_stretcher->getLatency() + m_reserve);
-//        cerr << "latency = " << *m_latency << endl;
+        *m_latency = getLatency();
     }
 
     updateCrispness();
     updateFormant();
-    updateFast();
 
     const int samples = insamples;
     int processed = 0;

          
@@ 496,17 502,6 @@ RubberBandPitchShifter::runImpl(unsigned
 
     float *ptrs[2];
 
-    int rs = m_outputBuffer[0]->getReadSpace();
-    if (rs < int(m_minfill)) {
-//        cerr << "temporary expansion (have " << rs << ", want " << m_reserve << ")" << endl;
-        m_stretcher->setTimeRatio(1.1); // fill up temporarily
-    } else if (rs > 8192) {
-//        cerr << "temporary reduction (have " << rs << ", want " << m_reserve << ")" << endl;
-        m_stretcher->setTimeRatio(0.9); // reduce temporarily
-    } else {
-        m_stretcher->setTimeRatio(1.0);
-    }
-
     while (processed < samples) {
 
         // never feed more than the minimum necessary number of

          
@@ 523,24 518,18 @@ RubberBandPitchShifter::runImpl(unsigned
 
         int avail = m_stretcher->available();
         int writable = m_outputBuffer[0]->getWriteSpace();
-        int outchunk = min(avail, writable);
+
+        int outchunk = avail;
+        if (outchunk > writable) {
+            cerr << "RubberBandPitchShifter::runImpl: buffer is not large enough: size = " << m_outputBuffer[0]->getSize() << ", chunk = " << outchunk << ", space = " << writable << " (buffer contains " << m_outputBuffer[0]->getReadSpace() << " unread)" << endl;
+            outchunk = writable;
+        }
+        
         size_t actual = m_stretcher->retrieve(m_scratch, outchunk);
         outTotal += actual;
 
-//        incount += inchunk;
-//        outcount += actual;
-
-//        cout << "avail: " << avail << ", outchunk = " << outchunk;
-//        if (actual != outchunk) cout << " (" << actual << ")";
-//        cout << endl;
-
-        outchunk = actual;
-
         for (size_t c = 0; c < m_channels; ++c) {
-            if (int(m_outputBuffer[c]->getWriteSpace()) < outchunk) {
-                cerr << "RubberBandPitchShifter::runImpl: buffer overrun: chunk = " << outchunk << ", space = " << m_outputBuffer[c]->getWriteSpace() << endl;
-            }                
-            m_outputBuffer[c]->write(m_scratch[c], outchunk);
+            m_outputBuffer[c]->write(m_scratch[c], actual);
         }
     }
     

          
@@ 553,8 542,9 @@ RubberBandPitchShifter::runImpl(unsigned
         m_outputBuffer[c]->read(&(m_output[c][offset]), chunk);
     }
 
-    if (m_minfill == 0) {
-        m_minfill = m_outputBuffer[0]->getReadSpace();
+    size_t fill = m_outputBuffer[0]->getReadSpace();
+    if (fill < m_minfill || m_minfill == 0) {
+        m_minfill = fill;
 //        cerr << "minfill = " << m_minfill << endl;
     }
 }

          
M ladspa/RubberBandPitchShifter.h +6 -4
@@ 48,7 48,7 @@ protected:
 	CentsPort        = 3,
         CrispnessPort    = 4,
 	FormantPort      = 5,
-	FastPort         = 6,
+	WetDryPort       = 6,
 	InputPort1       = 7,
         OutputPort1      = 8,
         PortCountMono    = OutputPort1 + 1,

          
@@ 83,7 83,8 @@ protected:
     void updateRatio();
     void updateCrispness();
     void updateFormant();
-    void updateFast();
+
+    int getLatency() const;
 
     float **m_input;
     float **m_output;

          
@@ 93,19 94,20 @@ protected:
     float *m_octaves;
     float *m_crispness;
     float *m_formant;
-    float *m_fast;
+    float *m_wetDry;
     double m_ratio;
     double m_prevRatio;
     int m_currentCrispness;
     bool m_currentFormant;
-    bool m_currentFast;
 
     size_t m_blockSize;
     size_t m_reserve;
+    size_t m_bufsize;
     size_t m_minfill;
 
     RubberBand::RubberBandStretcher *m_stretcher;
     RubberBand::RingBuffer<float> **m_outputBuffer;
+    RubberBand::RingBuffer<float> **m_delayMixBuffer;
     float **m_scratch;
 
     int m_sampleRate;

          
M main/main.cpp +417 -182
@@ 21,7 21,7 @@ 
     you must obtain a valid commercial licence before doing so.
 */
 
-#include "rubberband/RubberBandStretcher.h"
+#include "../rubberband/RubberBandStretcher.h"
 
 #include <iostream>
 #include <sndfile.h>

          
@@ 33,20 33,17 @@ 
 
 #include <fstream>
 
-#include "system/sysutils.h"
+#include "../src/system/sysutils.h"
 
 #ifdef _MSC_VER
-#include "getopt/getopt.h"
+#include "../src/getopt/getopt.h"
 #else
 #include <getopt.h>
 #include <unistd.h>
 #include <sys/time.h>
 #endif
 
-#include "base/Profiler.h"
-
-using namespace std;
-using namespace RubberBand;
+#include "../src/base/Profiler.h"
 
 #ifdef _WIN32
 using RubberBand::gettimeofday;

          
@@ 57,6 54,11 @@ using RubberBand::usleep;
 #define strdup _strdup
 #endif
 
+using RubberBand::RubberBandStretcher;
+
+using std::cerr;
+using std::endl;
+
 double tempo_convert(const char *str)
 {
     char *d = strchr((char *)str, ':');

          
@@ 105,7 107,10 @@ int main(int argc, char **argv)
 
     bool haveRatio = false;
 
-    std::string mapfile;
+    std::string timeMapFile;
+    std::string freqMapFile;
+    std::string pitchMapFile;
+    bool freqOrPitchMapSpecified = false;
 
     enum {
         NoTransients,

          
@@ 119,6 124,8 @@ int main(int argc, char **argv)
         SoftDetector
     } detector = CompoundDetector;
 
+    bool ignoreClipping = false;
+
     while (1) {
         int optionIndex = 0;
 

          
@@ 151,6 158,9 @@ int main(int argc, char **argv)
             { "threads",       0, 0, '@' },
             { "quiet",         0, 0, 'q' },
             { "timemap",       1, 0, 'M' },
+            { "freqmap",       1, 0, 'Q' },
+            { "pitchmap",      1, 0, 'C' },
+            { "ignore-clipping", 0, 0, 'i' },
             { 0, 0, 0, 0 }
         };
 

          
@@ 171,7 181,7 @@ int main(int argc, char **argv)
         case 'R': realtime = true; break;
         case 'L': precise = false; break;
         case 'P': precise = true; break;
-	case 'F': formant = true; break;
+        case 'F': formant = true; break;
         case '0': threading = 1; break;
         case '@': threading = 2; break;
         case '1': transients = NoTransients; crispchanged = true; break;

          
@@ 186,7 196,10 @@ int main(int argc, char **argv)
         case '%': hqpitch = true; break;
         case 'c': crispness = atoi(optarg); break;
         case 'q': quiet = true; break;
-        case 'M': mapfile = optarg; break;
+        case 'M': timeMapFile = optarg; break;
+        case 'Q': freqMapFile = optarg; freqOrPitchMapSpecified = true; break;
+        case 'C': pitchMapFile = optarg; freqOrPitchMapSpecified = true; break;
+        case 'i': ignoreClipping = true; break;
         default:  help = true; break;
         }
     }

          
@@ 196,6 209,15 @@ int main(int argc, char **argv)
         return 0;
     }
 
+    if (freqOrPitchMapSpecified) {
+        if (freqMapFile != "" && pitchMapFile != "") {
+            cerr << "ERROR: Please specify either pitch map or frequency map, not both" << endl;
+            return 1;
+        }
+        haveRatio = true;
+        realtime = true;
+    }
+    
     if (help || !haveRatio || optind + 2 != argc) {
         cerr << endl;
 	cerr << "Rubber Band" << endl;

          
@@ 214,23 236,45 @@ int main(int argc, char **argv)
         cerr << "  -p<X>, --pitch <X>      Raise pitch by X semitones, or" << endl;
         cerr << "  -f<X>, --frequency <X>  Change frequency by multiple X" << endl;
         cerr << endl;
-        cerr << "  -M<F>, --timemap <F>    Use file F as the source for key frame map" << endl;
+        cerr << "The following options provide ways of making the time and frequency ratios" << endl;
+        cerr << "change during the audio." << endl;
+        cerr << endl;
+        cerr << "  -M<F>, --timemap <F>    Use file F as the source for time map" << endl;
+        cerr << endl;
+        cerr << "  A time map (or key-frame map) file contains a series of lines, each with two" << endl;
+        cerr << "  sample frame numbers separated by a single space. These are source and" << endl;
+        cerr << "  target frames for fixed time points within the audio data, defining a varying" << endl;
+        cerr << "  stretch factor through the audio. When supplying a time map you must specify" << endl;
+        cerr << "  an overall stretch factor using -t, -T, or -D as well, to determine the" << endl;
+        cerr << "  total output duration." << endl;
+        cerr << endl;
+        cerr << "         --pitchmap <F>   Use file F as the source for pitch map" << endl;
         cerr << endl;
-        cerr << "A map file consists of a series of lines each having two numbers separated" << endl;
-        cerr << "by a single space.  These are source and target sample frame numbers for fixed" << endl;
-        cerr << "time points within the audio data, defining a varying stretch factor through" << endl;
-        cerr << "the audio.  You must specify an overall stretch factor using e.g. -t as well." << endl;
+        cerr << "  A pitch map file contains a series of lines, each with two values: the input" << endl;
+        cerr << "  sample frame number and a pitch offset in semitones, separated by a single" << endl;
+        cerr << "  space. These specify a varying pitch factor through the audio. The offsets" << endl;
+        cerr << "  are all relative to an initial offset specified by the pitch or frequency" << endl;
+        cerr << "  option, or relative to no shift if neither was specified. Offsets are" << endl;
+        cerr << "  not cumulative. This option implies realtime mode (-R) and also enables a" << endl;
+        cerr << "  high-consistency pitch shifting mode, appropriate for dynamic pitch changes." << endl;
+        cerr << "  Because of the use of realtime mode, the overall duration will not be exact." << endl;
         cerr << endl;
-        cerr << "The following options provide a simple way to adjust the sound.  See below" << endl;
+        cerr << "         --freqmap <F>    Use file F as the source for frequency map" << endl;
+        cerr << endl;
+        cerr << "  A frequency map file is like a pitch map, except that its second column" << endl;
+        cerr << "  lists frequency multipliers rather than pitch offsets (like the difference" << endl;
+        cerr << "  between pitch and frequency options above)." << endl;
+        cerr << endl;
+        cerr << "The following options provide a simple way to adjust the sound. See below" << endl;
         cerr << "for more details." << endl;
         cerr << endl;
         cerr << "  -c<N>, --crisp <N>      Crispness (N = 0,1,2,3,4,5,6); default 5 (see below)" << endl;
-	cerr << "  -F,    --formant        Enable formant preservation when pitch shifting" << endl;
+        cerr << "  -F,    --formant        Enable formant preservation when pitch shifting" << endl;
         cerr << endl;
         cerr << "The remaining options fine-tune the processing mode and stretch algorithm." << endl;
         cerr << "These are mostly included for test purposes; the default settings and standard" << endl;
         cerr << "crispness parameter are intended to provide the best sounding set of options" << endl;
-        cerr << "for most situations.  The default is to use none of these options." << endl;
+        cerr << "for most situations. The default is to use none of these options." << endl;
         cerr << endl;
         cerr << "  -L,    --loose          Relax timing in hope of better transient preservation" << endl;
         cerr << "  -P,    --precise        Ignored: The opposite of -L, this is default from 1.6" << endl;

          
@@ 248,6 292,8 @@ int main(int argc, char **argv)
         cerr << "         --pitch-hq       In RT mode, use a slower, higher quality pitch shift" << endl;
         cerr << "         --centre-focus   Preserve focus of centre material in stereo" << endl;
         cerr << "                          (at a cost in width and individual channel quality)" << endl;
+        cerr << "         --ignore-clipping Ignore clipping at output; the default is to restart" << endl;
+        cerr << "                           with reduced gain if clipping occurs" << endl;
         cerr << endl;
         cerr << "  -d<N>, --debug <N>      Select debug level (N = 0,1,2,3); default 0, full 3" << endl;
         cerr << "                          (N.B. debug level 3 includes audible ticks in output)" << endl;

          
@@ 265,7 311,7 @@ int main(int argc, char **argv)
         cerr << "  -c 5   default processing options" << endl;
         cerr << "  -c 6   equivalent to --no-lamination --window-short (may be good for drums)" << endl;
         cerr << endl;
-	return 2;
+        return 2;
     }
 
     if (ratio <= 0.0) {

          
@@ 278,6 324,12 @@ int main(int argc, char **argv)
         cerr << "         provided -- crispness will override these other options" << endl;
     }
 
+    if (hqpitch && freqOrPitchMapSpecified) {
+        cerr << "WARNING: High-quality pitch mode selected, but frequency or pitch map file is" << endl;
+        cerr << "         provided -- pitch mode will be overridden by high-consistency mode" << endl;
+        hqpitch = false;
+    }
+
     switch (crispness) {
     case -1: crispness = 5; break;
     case 0: detector = CompoundDetector; transients = NoTransients; lamination = false; longwin = true; shortwin = false; break;

          
@@ 303,34 355,35 @@ int main(int argc, char **argv)
         cerr << ")" << endl;
     }
 
-    std::map<size_t, size_t> mapping;
-    
-    if (mapfile != "") {
-        std::ifstream ifile(mapfile.c_str());
+    std::map<size_t, size_t> timeMap;
+    if (timeMapFile != "") {
+        std::ifstream ifile(timeMapFile.c_str());
         if (!ifile.is_open()) {
-            cerr << "ERROR: Failed to open time map file \"" << mapfile << "\""
-                 << endl;
+            cerr << "ERROR: Failed to open time map file \""
+                 << timeMapFile << "\"" << endl;
             return 1;
         }
         std::string line;
         int lineno = 0;
         while (!ifile.eof()) {
             std::getline(ifile, line);
-            while (line.length() > 0 && line[0] == ' ') line = line.substr(1);
+            while (line.length() > 0 && line[0] == ' ') {
+                line = line.substr(1);
+            }
             if (line == "") {
                 ++lineno;
                 continue;
             }
             std::string::size_type i = line.find_first_of(" ");
             if (i == std::string::npos) {
-                cerr << "ERROR: Time map file \"" << mapfile
+                cerr << "ERROR: Time map file \"" << timeMapFile
                      << "\" is malformed at line " << lineno << endl;
                 return 1;
             }
             size_t source = atoi(line.substr(0, i).c_str());
             while (i < line.length() && line[i] == ' ') ++i;
             size_t target = atoi(line.substr(i).c_str());
-            mapping[source] = target;
+            timeMap[source] = target;
             if (debug > 0) {
                 cerr << "adding mapping from " << source << " to " << target << endl;
             }

          
@@ 339,7 392,57 @@ int main(int argc, char **argv)
         ifile.close();
 
         if (!quiet) {
-            cerr << "Read " << mapping.size() << " line(s) from map file" << endl;
+            cerr << "Read " << timeMap.size() << " line(s) from time map file" << endl;
+        }
+    }
+
+    std::map<size_t, double> freqMap;
+
+    if (freqOrPitchMapSpecified) {
+        std::string file = freqMapFile;
+        bool convertFromPitch = false;
+        if (pitchMapFile != "") {
+            file = pitchMapFile;
+            convertFromPitch = true;
+        }
+        std::ifstream ifile(file.c_str());
+        if (!ifile.is_open()) {
+            cerr << "ERROR: Failed to open map file \"" << file << "\"" << endl;
+            return 1;
+        }
+        std::string line;
+        int lineno = 0;
+        while (!ifile.eof()) {
+            std::getline(ifile, line);
+            while (line.length() > 0 && line[0] == ' ') {
+                line = line.substr(1);
+            }
+            if (line == "") {
+                ++lineno;
+                continue;
+            }
+            std::string::size_type i = line.find_first_of(" ");
+            if (i == std::string::npos) {
+                cerr << "ERROR: Map file \"" << file
+                     << "\" is malformed at line " << lineno << endl;
+                return 1;
+            }
+            size_t source = atoi(line.substr(0, i).c_str());
+            while (i < line.length() && line[i] == ' ') ++i;
+            double freq = atof(line.substr(i).c_str());
+            if (convertFromPitch) {
+                freq = pow(2.0, freq / 12.0);
+            }
+            freqMap[source] = freq;
+            if (debug > 0) {
+                cerr << "adding mapping for source frame " << source << " of frequency multiplier " << freq << endl;
+            }
+            ++lineno;
+        }
+        ifile.close();
+
+        if (!quiet) {
+            cerr << "Read " << freqMap.size() << " line(s) from frequency map file" << endl;
         }
     }
 

          
@@ 355,9 458,9 @@ int main(int argc, char **argv)
 
     sndfile = sf_open(fileName, SFM_READ, &sfinfo);
     if (!sndfile) {
-	cerr << "ERROR: Failed to open input file \"" << fileName << "\": "
-	     << sf_strerror(sndfile) << endl;
-	return 1;
+        cerr << "ERROR: Failed to open input file \"" << fileName << "\": "
+             << sf_strerror(sndfile) << endl;
+        return 1;
     }
 
     if (sfinfo.samplerate == 0) {

          
@@ 383,13 486,10 @@ int main(int argc, char **argv)
     
     sndfileOut = sf_open(fileNameOut, SFM_WRITE, &sfinfoOut) ;
     if (!sndfileOut) {
-	cerr << "ERROR: Failed to open output file \"" << fileNameOut << "\" for writing: "
-	     << sf_strerror(sndfileOut) << endl;
-	return 1;
+        cerr << "ERROR: Failed to open output file \"" << fileNameOut << "\" for writing: "
+             << sf_strerror(sndfileOut) << endl;
+        return 1;
     }
-    
-    int ibs = 1024;
-    size_t channels = sfinfo.channels;
 
     RubberBandStretcher::Options options = 0;
     if (realtime)    options |= RubberBandStretcher::OptionProcessRealTime;

          
@@ 399,9 499,14 @@ int main(int argc, char **argv)
     if (shortwin)    options |= RubberBandStretcher::OptionWindowShort;
     if (smoothing)   options |= RubberBandStretcher::OptionSmoothingOn;
     if (formant)     options |= RubberBandStretcher::OptionFormantPreserved;
-    if (hqpitch)     options |= RubberBandStretcher::OptionPitchHighQuality;
     if (together)    options |= RubberBandStretcher::OptionChannelsTogether;
 
+    if (freqOrPitchMapSpecified) {
+        options |= RubberBandStretcher::OptionPitchHighConsistency;
+    } else if (hqpitch) {
+        options |= RubberBandStretcher::OptionPitchHighQuality;
+    }
+    
     switch (threading) {
     case 0:
         options |= RubberBandStretcher::OptionThreadingAuto;

          
@@ 439,56 544,267 @@ int main(int argc, char **argv)
     }
 
     if (pitchshift != 0.0) {
-        frequencyshift *= pow(2.0, pitchshift / 12);
+        frequencyshift *= pow(2.0, pitchshift / 12.0);
     }
 
     cerr << "Using time ratio " << ratio;
-    cerr << " and frequency ratio " << frequencyshift << endl;
 
+    if (!freqOrPitchMapSpecified) {
+        cerr << " and frequency ratio " << frequencyshift << endl;
+    } else {
+        cerr << " and initial frequency ratio " << frequencyshift << endl;
+    }
+    
 #ifdef _WIN32
     RubberBand::
 #endif
     timeval tv;
     (void)gettimeofday(&tv, 0);
-
+    
     RubberBandStretcher::setDefaultDebugLevel(debug);
 
-    RubberBandStretcher ts(sfinfo.samplerate, channels, options,
-                           ratio, frequencyshift);
+    size_t countIn = 0, countOut = 0;
+
+    float gain = 1.f;
+    bool successful = false;
+    
+    const size_t channels = sfinfo.channels;
+    const int bs = 1024;
+    
+    float **cbuf = new float *[channels];
+    for (size_t c = 0; c < channels; ++c) {
+        cbuf[c] = new float[bs];
+    }
+    float *ibuf = new float[channels * bs];
 
-    ts.setExpectedInputDuration(sfinfo.frames);
+    int thisBlockSize;
+
+    while (!successful) { // we may have to repeat with a modified
+                          // gain, if clipping occurs
+        successful = true;
 
-    float *fbuf = new float[channels * ibs];
-    float **ibuf = new float *[channels];
-    for (size_t i = 0; i < channels; ++i) ibuf[i] = new float[ibs];
+        RubberBandStretcher ts(sfinfo.samplerate, channels, options,
+                               ratio, frequencyshift);
+        ts.setExpectedInputDuration(sfinfo.frames);
+
+        int frame = 0;
+        int percent = 0;
+
+        sf_seek(sndfile, 0, SEEK_SET);
+
+        if (!realtime) {
 
-    int frame = 0;
-    int percent = 0;
+            if (!quiet) {
+                cerr << "Pass 1: Studying..." << endl;
+            }
+
+            while (frame < sfinfo.frames) {
 
-    sf_seek(sndfile, 0, SEEK_SET);
+                int count = -1;
+                if ((count = sf_readf_float(sndfile, ibuf, bs)) <= 0) break;
+        
+                for (size_t c = 0; c < channels; ++c) {
+                    for (int i = 0; i < count; ++i) {
+                        cbuf[c][i] = ibuf[i * channels + c];
+                    }
+                }
+
+                bool final = (frame + bs >= sfinfo.frames);
 
-    if (!realtime) {
+                ts.study(cbuf, count, final);
 
-        if (!quiet) {
-            cerr << "Pass 1: Studying..." << endl;
+                int p = int((double(frame) * 100.0) / sfinfo.frames);
+                if (p > percent || frame == 0) {
+                    percent = p;
+                    if (!quiet) {
+                        cerr << "\r" << percent << "% ";
+                    }
+                }
+
+                frame += bs;
+            }
+
+            if (!quiet) {
+                cerr << "\rCalculating profile..." << endl;
+            }
+
+            sf_seek(sndfile, 0, SEEK_SET);
         }
 
+        frame = 0;
+        percent = 0;
+
+        if (!timeMap.empty()) {
+            ts.setKeyFrameMap(timeMap);
+        }
+
+        std::map<size_t, double>::const_iterator freqMapItr = freqMap.begin();
+    
+        countIn = 0;
+        countOut = 0;
+        bool clipping = false;
+
+        // The stretcher only pads the start in offline mode; to avoid
+        // a fade in at the start, we pad it manually in RT mode
+        int toDrop = 0;
+        if (realtime) {
+            toDrop = int(ts.getLatency());
+            int toPad = int(round(toDrop * frequencyshift));
+            if (debug > 0) {
+                cerr << "padding start with " << toPad
+                     << " samples in RT mode, will drop " << toDrop
+                     << " at output" << endl;
+            }
+            if (toPad > 0) {
+                for (size_t c = 0; c < channels; ++c) {
+                    for (int i = 0; i < bs; ++i) {
+                        cbuf[c][i] = 0.f;
+                    }
+                }
+                while (toPad > 0) {
+                    int p = toPad;
+                    if (p > bs) p = bs;
+                    ts.process(cbuf, p, false);
+                    toPad -= p;
+                }
+            }
+        }                
+        
         while (frame < sfinfo.frames) {
 
-            int count = -1;
+            thisBlockSize = bs;
 
-            if ((count = sf_readf_float(sndfile, fbuf, ibs)) <= 0) break;
-        
-            for (size_t c = 0; c < channels; ++c) {
-                for (int i = 0; i < count; ++i) {
-                    float value = fbuf[i * channels + c];
-                    ibuf[c][i] = value;
+            while (freqMapItr != freqMap.end()) {
+                size_t nextFreqFrame = freqMapItr->first;
+                if (nextFreqFrame <= countIn) {
+                    double s = frequencyshift * freqMapItr->second;
+                    if (debug > 0) {
+                        cerr << "at frame " << countIn
+                             << " (requested at " << freqMapItr->first
+                             << " [NOT] plus latency " << ts.getLatency()
+                             << ") updating frequency ratio to " << s << endl;
+                    }
+                    ts.setPitchScale(s);
+                    ++freqMapItr;
+                } else {
+                    if (nextFreqFrame < countIn + thisBlockSize) {
+                        thisBlockSize = nextFreqFrame - countIn;
+                    }
+                    break;
                 }
             }
 
-            bool final = (frame + ibs >= sfinfo.frames);
+            int count = -1;
+            if ((count = sf_readf_float(sndfile, ibuf, thisBlockSize)) < 0) {
+                break;
+            }
+        
+            countIn += count;
+
+            for (size_t c = 0; c < channels; ++c) {
+                for (int i = 0; i < count; ++i) {
+                    cbuf[c][i] = ibuf[i * channels + c];
+                }
+            }
+
+            bool final = (frame + thisBlockSize >= sfinfo.frames);
+
+            if (debug > 2) {
+                cerr << "count = " << count << ", bs = " << thisBlockSize << ", frame = " << frame << ", frames = " << sfinfo.frames << ", final = " << final << endl;
+            }
+
+            ts.process(cbuf, count, final);
+
+            int avail;
+            while ((avail = ts.available()) > 0) {
+                if (debug > 1) {
+                    cerr << "available = " << avail << endl;
+                }
 
-            ts.study(ibuf, count, final);
+                thisBlockSize = avail;
+                if (thisBlockSize > bs) {
+                    thisBlockSize = bs;
+                }
+                
+                if (toDrop > 0) {
+                    int dropHere = toDrop;
+                    if (dropHere > thisBlockSize) {
+                        dropHere = thisBlockSize;
+                    }
+                    if (debug > 1) {
+                        cerr << "toDrop = " << toDrop << ", dropping "
+                             << dropHere << " of " << avail << endl;
+                    }
+                    ts.retrieve(cbuf, dropHere);
+                    toDrop -= dropHere;
+                    avail -= dropHere;
+                    continue;
+                }
+                
+                if (debug > 2) {
+                    cerr << "retrieving block of " << thisBlockSize << endl;
+                }
+                ts.retrieve(cbuf, thisBlockSize);
+                
+                if (realtime && final) {
+                    // (in offline mode the stretcher handles this itself)
+                    size_t ideal = size_t(countIn * ratio);
+                    if (debug > 2) {
+                        cerr << "at end, ideal = " << ideal
+                             << ", countOut = " << countOut
+                             << ", thisBlockSize = " << thisBlockSize << endl;
+                    }
+                    if (countOut + thisBlockSize > ideal) {
+                        thisBlockSize = ideal - countOut;
+                        if (debug > 1) {
+                            cerr << "truncated final block to " << thisBlockSize
+                                 << endl;
+                        }
+                    }
+                }
+                
+                countOut += thisBlockSize;
+                
+                for (size_t c = 0; c < channels; ++c) {
+                    for (int i = 0; i < thisBlockSize; ++i) {
+                        float value = gain * cbuf[c][i];
+                        if (ignoreClipping) { // i.e. just clamp, don't bail out
+                            if (value > 1.f) value = 1.f;
+                            if (value < -1.f) value = -1.f;
+                        } else {
+                            if (value >= 1.f || value < -1.f) {
+                                clipping = true;
+                                gain = (0.999f / fabsf(cbuf[c][i]));
+                            }
+                        }
+                        ibuf[i * channels + c] = value;
+                    }
+                }
+                sf_writef_float(sndfileOut, ibuf, thisBlockSize);
+            }
+
+            if (clipping) {
+                if (!quiet) {
+                    cerr << "NOTE: Clipping detected at output sample "
+                         << countOut << ", restarting with "
+                         << "reduced gain of " << gain
+                         << " (supply --ignore-clipping to avoid this)" << endl;
+                }
+                const float mingain = 0.75f;
+                if (gain < mingain) {
+                    cerr << "WARNING: Clipped values were implausibly high: "
+                         << "something wrong with input or process - "
+                         << "not reducing gain below " << mingain << endl;
+                    gain = mingain;
+                    ignoreClipping = true;
+                }
+                successful = false;
+                break;
+            }
+            
+            if (frame == 0 && !realtime && !quiet) {
+                cerr << "Pass 2: Processing..." << endl;
+            }
 
             int p = int((double(frame) * 100.0) / sfinfo.frames);
             if (p > percent || frame == 0) {

          
@@ 498,140 814,62 @@ int main(int argc, char **argv)
                 }
             }
 
-            frame += ibs;
+            frame += count;
         }
 
+        if (!successful) {
+            sf_seek(sndfile, 0, SEEK_SET);
+            sf_seek(sndfileOut, 0, SEEK_SET);
+            continue;
+        }
+    
         if (!quiet) {
-            cerr << "\rCalculating profile..." << endl;
+            cerr << "\r    " << endl;
         }
 
-        sf_seek(sndfile, 0, SEEK_SET);
-    }
-
-    frame = 0;
-    percent = 0;
-
-    if (!mapping.empty()) {
-        ts.setKeyFrameMap(mapping);
-    }
-    
-    size_t countIn = 0, countOut = 0;
-
-    while (frame < sfinfo.frames) {
-
-        int count = -1;
-
-	if ((count = sf_readf_float(sndfile, fbuf, ibs)) < 0) break;
-        
-        countIn += count;
-
-        for (size_t c = 0; c < channels; ++c) {
-            for (int i = 0; i < count; ++i) {
-                float value = fbuf[i * channels + c];
-                ibuf[c][i] = value;
+        int avail;
+        while ((avail = ts.available()) >= 0) {
+            if (debug > 1) {
+                cerr << "(completing) available = " << avail << endl;
             }
-        }
 
-        bool final = (frame + ibs >= sfinfo.frames);
-
-        if (debug > 2) {
-            cerr << "count = " << count << ", ibs = " << ibs << ", frame = " << frame << ", frames = " << sfinfo.frames << ", final = " << final << endl;
-        }
-
-        ts.process(ibuf, count, final);
-
-        int avail = ts.available();
-        if (debug > 1) cerr << "available = " << avail << endl;
-
-        if (avail > 0) {
-            float **obf = new float *[channels];
-            for (size_t i = 0; i < channels; ++i) {
-                obf[i] = new float[avail];
-            }
-            ts.retrieve(obf, avail);
-            countOut += avail;
-            float *fobf = new float[channels * avail];
-            for (size_t c = 0; c < channels; ++c) {
-                for (int i = 0; i < avail; ++i) {
-                    float value = obf[c][i];
-                    if (value > 1.f) value = 1.f;
-                    if (value < -1.f) value = -1.f;
-                    fobf[i * channels + c] = value;
+            if (avail == 0) {
+                if (realtime ||
+                    (options & RubberBandStretcher::OptionThreadingNever)) {
+                    break;
+                } else {
+                    usleep(10000);
                 }
             }
-//            cout << "fobf mean: ";
-//    double d = 0;
-//    for (int i = 0; i < avail; ++i) {
-//        d += fobf[i];
-//    }
-//    d /= avail;
-//    cout << d << endl;
-            sf_writef_float(sndfileOut, fobf, avail);
-            delete[] fobf;
-            for (size_t i = 0; i < channels; ++i) {
-                delete[] obf[i];
-            }
-            delete[] obf;
-        }
-
-        if (frame == 0 && !realtime && !quiet) {
-            cerr << "Pass 2: Processing..." << endl;
-        }
-
-	int p = int((double(frame) * 100.0) / sfinfo.frames);
-	if (p > percent || frame == 0) {
-	    percent = p;
-            if (!quiet) {
-                cerr << "\r" << percent << "% ";
+            
+            thisBlockSize = avail;
+            if (thisBlockSize > bs) {
+                thisBlockSize = bs;
             }
-	}
-
-        frame += ibs;
-    }
-
-    if (!quiet) {
-        cerr << "\r    " << endl;
-    }
-    int avail;
-
-    while ((avail = ts.available()) >= 0) {
+                
+            ts.retrieve(cbuf, thisBlockSize);
 
-        if (debug > 1) {
-            cerr << "(completing) available = " << avail << endl;
-        }
-
-        if (avail > 0) {
-            float **obf = new float *[channels];
-            for (size_t i = 0; i < channels; ++i) {
-                obf[i] = new float[avail];
-            }
-            ts.retrieve(obf, avail);
-            countOut += avail;
-            float *fobf = new float[channels * avail];
+            countOut += thisBlockSize;
+                
             for (size_t c = 0; c < channels; ++c) {
-                for (int i = 0; i < avail; ++i) {
-                    float value = obf[c][i];
+                for (int i = 0; i < thisBlockSize; ++i) {
+                    float value = gain * cbuf[c][i];
                     if (value > 1.f) value = 1.f;
                     if (value < -1.f) value = -1.f;
-                    fobf[i * channels + c] = value;
+                    ibuf[i * channels + c] = value;
                 }
             }
-
-            sf_writef_float(sndfileOut, fobf, avail);
-            delete[] fobf;
-            for (size_t i = 0; i < channels; ++i) {
-                delete[] obf[i];
-            }
-            delete[] obf;
-        } else {
-            usleep(10000);
+                
+            sf_writef_float(sndfileOut, ibuf, thisBlockSize);
         }
     }
 
-    delete[] fbuf;
+    delete[] ibuf;
 
-    for (size_t i = 0; i < channels; ++i) delete[] ibuf[i];
-    delete[] ibuf;
+    for (size_t c = 0; c < channels; ++c) {
+        delete[] cbuf[c];
+    }
+    delete[] cbuf;
 
     sf_close(sndfile);
     sf_close(sndfileOut);

          
@@ 646,7 884,7 @@ int main(int argc, char **argv)
 #ifdef _WIN32
         RubberBand::
 #endif
-        timeval etv;
+            timeval etv;
         (void)gettimeofday(&etv, 0);
         
         etv.tv_sec -= tv.tv_sec;

          
@@ 657,10 895,7 @@ int main(int argc, char **argv)
         etv.tv_usec -= tv.tv_usec;
         
         double sec = double(etv.tv_sec) + (double(etv.tv_usec) / 1000000.0);
-        cerr << "elapsed time: " << sec
-             << " sec, in frames/sec: " << int64_t(round(countIn/sec))
-             << ", out frames/sec: " << int64_t(round(countOut/sec))
-             << endl;
+        cerr << "elapsed time: " << sec << " sec, in frames/sec: " << countIn/sec << ", out frames/sec: " << countOut/sec << endl;
     }
 
     RubberBand::Profiler::dump();

          
M meson.build +11 -5
@@ 2,7 2,7 @@ 
 project(
   'Rubber Band Library',
   'c', 'cpp',
-  version: '1.9.2',
+  version: '2.0.0',
   license: 'GPL-2.0-or-later',
   default_options: [
     # All Rubber Band code is actually C++98, but some compilers no

          
@@ 15,7 15,7 @@ project(
   meson_version: '>= 0.53.0'
 )
 
-rubberband_dynamic_library_version = '2.1.4'
+rubberband_dynamic_library_version = '2.1.5'
 
 system = host_machine.system()
 architecture = host_machine.cpu_family()

          
@@ 140,7 140,7 @@ if resampler == 'auto'
   if samplerate_dep.found()
     resampler = 'libsamplerate'
   else
-    resampler = 'speex'
+    resampler = 'builtin'
   endif
 endif
 

          
@@ 199,7 199,13 @@ else
 
 endif # fft
 
-if resampler == 'libsamplerate'
+if resampler == 'builtin'
+  config_summary += { 'Resampler': 'Built-in' }
+  message('For resampler: using built-in implementation')
+  library_sources += 'src/dsp/BQResampler.cpp'
+  feature_defines += ['-DUSE_BQRESAMPLER']
+
+elif resampler == 'libsamplerate'
   if samplerate_dep.found()
     config_summary += { 'Resampler': 'libsamplerate' }
     message('For resampler: using libsamplerate')

          
@@ 441,7 447,7 @@ if not get_option('no_shared')
   message('Will build Rubber Band Library shared library')
   rubberband_dynamic = shared_library(
     rubberband_dynamic_name,
-    objects: rubberband_static.extract_all_objects(),
+    objects: rubberband_static.extract_all_objects(recursive: true),
     link_args: [
       arch_flags,
       feature_libraries,

          
M meson_options.txt +2 -2
@@ 7,9 7,9 @@ option('fft',
 
 option('resampler',
        type: 'combo',
-       choices: ['auto', 'libsamplerate', 'speex', 'ipp'],
+       choices: ['auto', 'builtin', 'libsamplerate', 'speex', 'ipp'],
        value: 'auto',
-       description: 'Resampler library to use. Recommended is libsamplerate. The default (auto) will use libsamplerate if available, speex otherwise.')
+       description: 'Resampler library to use. The default (auto) will use libsamplerate if available, the builtin implementation otherwise.')
 
 option('ipp_path',
        type: 'string',

          
M otherbuilds/Makefile.linux +1 -1
@@ 6,7 6,7 @@ OPTFLAGS	:= -DNDEBUG -ffast-math -O3 -ft
 
 ARCHFLAGS	:= 
 
-CXXFLAGS	:= -std=c++98 $(ARCHFLAGS) $(OPTFLAGS) -I. -Isrc -Irubberband -DHAVE_LIBSAMPLERATE -DUSE_BUILTIN_FFT -DNO_THREAD_CHECKS -DUSE_PTHREADS -DNO_TIMING -DHAVE_POSIX_MEMALIGN -DNDEBUG
+CXXFLAGS	:= -std=c++11 $(ARCHFLAGS) $(OPTFLAGS) -I. -Isrc -Irubberband -DHAVE_LIBSAMPLERATE -DUSE_BUILTIN_FFT -DNO_THREAD_CHECKS -DUSE_PTHREADS -DNO_TIMING -DHAVE_POSIX_MEMALIGN -DNDEBUG
 
 CFLAGS		:= $(ARCHFLAGS) $(OPTFLAGS)
 

          
M otherbuilds/check.sh +35 -1
@@ 1,13 1,47 @@ 
 #!/bin/bash
 set -eu
+
 if [ ! -d /Applications ]; then
+    # Assumed to be Linux
+
+    echo " *** Building static library using Linux-specific Makefile"
+#    make -f otherbuilds/Makefile.linux clean
     make -f otherbuilds/Makefile.linux
+    
+    echo " *** Linking against static library"
     g++ main/main.cpp lib/librubberband.a -I. -Isrc -o test -lsndfile -lsamplerate -lpthread
+    
+    echo " *** Running build from Linux-specific Makefile"
     ./test -V
+    
+    echo " *** Building with single-file source"
+    g++ main/main.cpp single/RubberBandSingle.cpp -o test_single -lsndfile
+    
+    echo " *** Running build from single-file source"
+    ./test_single -V
+    
+    echo " *** OK"
+    
 else
+
+    echo " *** Building static library using macOS-specific Makefile"
+    make -f otherbuilds/Makefile.macos clean
     make -f otherbuilds/Makefile.macos
+
+    echo " *** Linking against static library"
     c++ main/main.cpp lib/librubberband.a -I. -Isrc -o test -lsndfile -lsamplerate -framework Accelerate
+    
+    echo " *** Running build from macOS-specific Makefile"
     ./test -V
-    make -f otherbuilds/Makefile.macos clean
+
+    echo " *** Building with single-file source"
+c++ main/main.cpp single/RubberBandSingle.cpp -o test_single -lsndfile -framework Accelerate
+
+    echo " *** Running build from single-file source"
+    ./test_single -V
+
+    echo " *** Building static library using iOS-specific Makefile"
+    make -f otherbuilds/Makefile.ios clean
     make -f otherbuilds/Makefile.ios
+    
 fi

          
M rubberband/RubberBandStretcher.h +1 -1
@@ 24,7 24,7 @@ 
 #ifndef RUBBERBAND_STRETCHER_H
 #define RUBBERBAND_STRETCHER_H
     
-#define RUBBERBAND_VERSION "1.9.2"
+#define RUBBERBAND_VERSION "2.0.0"
 #define RUBBERBAND_API_MAJOR_VERSION 2
 #define RUBBERBAND_API_MINOR_VERSION 6
 

          
M rubberband/rubberband-c.h +1 -1
@@ 28,7 28,7 @@ 
 extern "C" {
 #endif
 
-#define RUBBERBAND_VERSION "1.9.2"
+#define RUBBERBAND_VERSION "2.0.0"
 #define RUBBERBAND_API_MAJOR_VERSION 2
 #define RUBBERBAND_API_MINOR_VERSION 6
 

          
A => single/RubberBandSingle.cpp +80 -0
@@ 0,0 1,80 @@ 
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Rubber Band Library
+    An audio time-stretching and pitch-shifting library.
+    Copyright 2007-2021 Particular Programs Ltd.
+
+    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.
+
+    Alternatively, if you have a valid commercial licence for the
+    Rubber Band Library obtained by agreement with the copyright
+    holders, you may redistribute and/or modify it under the terms
+    described in that licence.
+
+    If you wish to distribute code using the Rubber Band Library
+    under terms other than those of the GNU General Public License,
+    you must obtain a valid commercial licence before doing so.
+*/
+
+/*
+    RubberBandSingle.cpp
+ 
+    This is a single-file compilation unit for Rubber Band Library.
+  
+    To use the library in your project without building it separately,
+    include in your code either rubberband/RubberBandStretcher.h for
+    use in C++ or rubberband/rubberband-c.h if you need the C
+    interface, then add this single C++ source file to your build.
+ 
+    Don't move this file into your source tree - keep it in the same
+    place relative to the other Rubber Band code, so that the relative
+    include paths work, and just add it to your list of build files.
+  
+    This produces a build using the built-in FFT and resampler
+    implementations, except on Apple platforms, where the vDSP FFT is
+    used (and where you will need the Accelerate framework when
+    linking). It should work correctly on any supported platform, but
+    may not be the fastest configuration available. For further
+    customisation, consider using the full build system to make a
+    standalone library.
+*/
+
+#define USE_BQRESAMPLER 1
+
+#define NO_TIMING 1
+#define NO_THREADING 1
+#define NO_THREAD_CHECKS 1
+
+#if defined(__APPLE__)
+#define HAVE_VDSP 1
+#else
+#define USE_BUILTIN_FFT 1
+#endif
+
+#include "../src/audiocurves/CompoundAudioCurve.cpp"
+#include "../src/audiocurves/SpectralDifferenceAudioCurve.cpp"
+#include "../src/audiocurves/HighFrequencyAudioCurve.cpp"
+#include "../src/audiocurves/SilentAudioCurve.cpp"
+#include "../src/audiocurves/ConstantAudioCurve.cpp"
+#include "../src/audiocurves/PercussiveAudioCurve.cpp"
+#include "../src/base/Profiler.cpp"
+#include "../src/dsp/AudioCurveCalculator.cpp"
+#include "../src/dsp/FFT.cpp"
+#include "../src/dsp/Resampler.cpp"
+#include "../src/dsp/BQResampler.cpp"
+#include "../src/system/Allocators.cpp"
+#include "../src/system/sysutils.cpp"
+#include "../src/system/Thread.cpp"
+#include "../src/RubberBandStretcher.cpp"
+#include "../src/StretchCalculator.cpp"
+#include "../src/StretcherChannelData.cpp"
+#include "../src/StretcherImpl.cpp"
+#include "../src/StretcherProcess.cpp"
+
+#include "../src/rubberband-c.cpp"
+

          
M src/StretchCalculator.cpp +171 -44
@@ 41,12 41,14 @@ StretchCalculator::StretchCalculator(siz
     m_sampleRate(sampleRate),
     m_increment(inputIncrement),
     m_prevDf(0),
-    m_divergence(0),
-    m_recovery(0),
     m_prevRatio(1.0),
+    m_prevTimeRatio(1.0),
     m_transientAmnesty(0),
     m_debugLevel(0),
-    m_useHardPeaks(useHardPeaks)
+    m_useHardPeaks(useHardPeaks),
+    m_inFrameCounter(0),
+    m_frameCheckpoint(0, 0),
+    m_outFrameCounter(0)
 {
 //    std::cerr << "StretchCalculator::StretchCalculator: useHardPeaks = " << useHardPeaks << std::endl;
 }    

          
@@ 318,18 320,108 @@ StretchCalculator::mapPeaks(std::vector<
     }
 }    
 
+int64_t
+StretchCalculator::expectedOutFrame(int64_t inFrame, double timeRatio)
+{
+    int64_t checkpointedAt = m_frameCheckpoint.first;
+    int64_t checkpointed = m_frameCheckpoint.second;
+    return int64_t(round(checkpointed + (inFrame - checkpointedAt) * timeRatio));
+}
+
 int
-StretchCalculator::calculateSingle(double ratio,
+StretchCalculator::calculateSingle(double timeRatio,
+                                   double effectivePitchRatio,
                                    float df,
-                                   size_t increment)
+                                   size_t inIncrement,
+                                   size_t analysisWindowSize,
+                                   size_t synthesisWindowSize)
 {
+    double ratio = timeRatio / effectivePitchRatio;
+    
+    int increment = int(inIncrement);
     if (increment == 0) increment = m_increment;
 
+    int outIncrement = lrint(increment * ratio); // the normal case
     bool isTransient = false;
-
+    
     // We want to ensure, as close as possible, that the phase reset
-    // points appear at _exactly_ the right audio frame numbers.
+    // points appear at the right audio frame numbers. To this end we
+    // track the incoming frame number, its corresponding expected
+    // output frame number, and the actual output frame number
+    // projected based on the ratios provided.
+    //
+    // There are two subtleties:
+    // 
+    // (1) on a ratio change, we need to checkpoint the expected
+    // output frame number reached so far and start counting again
+    // with the new ratio. We could do this with a reset to zero, but
+    // it's easier to reason about absolute input/output frame
+    // matches, so for the moment at least we're doing this by
+    // explicitly checkpointing the current numbers (hence the use of
+    // the above expectedOutFrame() function which refers to the
+    // last checkpointed values).
+    //
+    // (2) in the case of a pitch shift in a configuration where
+    // resampling occurs after stretching, all of our output
+    // increments will be effectively modified by resampling after we
+    // return. This is why we separate out timeRatio and
+    // effectivePitchRatio arguments - the former is the ratio that
+    // has already been applied and the latter is the ratio that will
+    // be applied by any subsequent resampling step (which will be 1.0
+    // / pitchScale if resampling is happening after stretching). So
+    // the overall ratio is timeRatio / effectivePitchRatio.
+
+    bool ratioChanged = (ratio != m_prevRatio);
+    if (ratioChanged) {
+        // Reset our frame counters from the ratio change.
+
+        // m_outFrameCounter tracks the frames counted at output from
+        // this function, which normally precedes resampling - hence
+        // the use of timeRatio rather than ratio here
+
+        if (m_debugLevel > 1) {
+            std::cerr << "StretchCalculator: ratio changed from " << m_prevRatio << " to " << ratio << std::endl;
+        }
 
+        int64_t toCheckpoint = expectedOutFrame
+            (m_inFrameCounter, m_prevTimeRatio);
+        m_frameCheckpoint =
+            std::pair<int64_t, int64_t>(m_inFrameCounter, toCheckpoint);
+    }
+    
+    m_prevRatio = ratio;
+    m_prevTimeRatio = timeRatio;
+
+    if (m_debugLevel > 2) {
+        std::cerr << "StretchCalculator::calculateSingle: timeRatio = "
+                  << timeRatio << ", effectivePitchRatio = "
+                  << effectivePitchRatio << " (that's 1.0 / "
+                  << (1.0 / effectivePitchRatio)
+                  << "), ratio = " << ratio << ", df = " << df
+                  << ", inIncrement = " << inIncrement
+                  << ", default outIncrement = " << outIncrement
+                  << ", analysisWindowSize = " << analysisWindowSize
+                  << ", synthesisWindowSize = " << synthesisWindowSize
+                  << std::endl;
+
+        std::cerr << "inFrameCounter = " << m_inFrameCounter
+                  << ", outFrameCounter = " << m_outFrameCounter
+                  << std::endl;
+
+        std::cerr << "The next sample out is input sample " << m_inFrameCounter << std::endl;
+    }
+    
+    int64_t intended = expectedOutFrame
+        (m_inFrameCounter + analysisWindowSize/4, timeRatio);
+    int64_t projected = int64_t
+        (round(m_outFrameCounter + (synthesisWindowSize/4 * effectivePitchRatio)));
+
+    int64_t divergence = projected - intended;
+
+    if (m_debugLevel > 2) {
+        std::cerr << "for current frame + quarter frame: intended " << intended << ", projected " << projected << ", divergence " << divergence << std::endl;
+    }
+    
     // In principle, the threshold depends on chunk size: larger chunk
     // sizes need higher thresholds.  Since chunk size depends on
     // ratio, I suppose we could in theory calculate the threshold

          
@@ 340,7 432,13 @@ StretchCalculator::calculateSingle(doubl
 //    if (ratio > 1) transientThreshold = 0.25f;
 
     if (m_useHardPeaks && df > m_prevDf * 1.1f && df > transientThreshold) {
-        isTransient = true;
+        if (divergence > 1000 || divergence < -1000) {
+            if (m_debugLevel > 1) {
+                std::cerr << "StretchCalculator::calculateSingle: transient, but we're not permitting it because the divergence (" << divergence << ") is too great" << std::endl;
+            }
+        } else {
+            isTransient = true;
+        }
     }
 
     if (m_debugLevel > 2) {

          
@@ 350,62 448,91 @@ StretchCalculator::calculateSingle(doubl
 
     m_prevDf = df;
 
-    bool ratioChanged = (ratio != m_prevRatio);
-    m_prevRatio = ratio;
-
-    if (isTransient && m_transientAmnesty == 0) {
+    if (m_transientAmnesty > 0) {
+        if (isTransient) {
+            if (m_debugLevel > 1) {
+                std::cerr << "StretchCalculator::calculateSingle: transient, but we have an amnesty (df " << df << ", threshold " << transientThreshold << ")" << std::endl;
+            }
+            isTransient = false;
+        }
+        --m_transientAmnesty;
+    }
+            
+    if (isTransient) {
         if (m_debugLevel > 1) {
-            std::cerr << "StretchCalculator::calculateSingle: transient (df " << df << ", threshold " << transientThreshold << ")" << std::endl;
+            std::cerr << "StretchCalculator::calculateSingle: transient at (df " << df << ", threshold " << transientThreshold << ")" << std::endl;
         }
-        m_divergence += increment - (increment * ratio);
 
         // as in offline mode, 0.05 sec approx min between transients
         m_transientAmnesty =
             lrint(ceil(double(m_sampleRate) / (20 * double(increment))));
 
-        m_recovery = m_divergence / ((m_sampleRate / 10.0) / increment);
-        return -int(increment);
-    }
+        outIncrement = increment;
+
+    } else {
+
+        double recovery = 0.0;
+        if (divergence > 1000 || divergence < -1000) {
+            recovery = divergence / ((m_sampleRate / 10.0) / increment);
+        } else if (divergence > 100 || divergence < -100) {
+            recovery = divergence / ((m_sampleRate / 20.0) / increment);
+        } else {
+            recovery = divergence / 4.0;
+        }
+
+        int incr = lrint(outIncrement - recovery);
+        if (m_debugLevel > 2 || (m_debugLevel > 1 && divergence != 0)) {
+            std::cerr << "divergence = " << divergence << ", recovery = " << recovery << ", incr = " << incr << ", ";
+        }
 
-    if (ratioChanged) {
-        m_recovery = m_divergence / ((m_sampleRate / 10.0) / increment);
+        int minIncr = lrint(increment * ratio * 0.3);
+        int maxIncr = lrint(increment * ratio * 2);
+        
+        if (incr < minIncr) {
+            incr = minIncr;
+        } else if (incr > maxIncr) {
+            incr = maxIncr;
+        }
+
+        if (m_debugLevel > 2 || (m_debugLevel > 1 && divergence != 0)) {
+            std::cerr << "clamped into [" << minIncr << ", " << maxIncr
+                      << "] becomes " << incr << std::endl;
+        }
+
+        if (incr < 0) {
+            std::cerr << "WARNING: internal error: incr < 0 in calculateSingle"
+                      << std::endl;
+            outIncrement = 0;
+        } else {
+            outIncrement = incr;
+        }
     }
 
-    if (m_transientAmnesty > 0) --m_transientAmnesty;
-
-    int incr = lrint(increment * ratio - m_recovery);
-    if (m_debugLevel > 2 || (m_debugLevel > 1 && m_divergence != 0)) {
-        std::cerr << "divergence = " << m_divergence << ", recovery = " << m_recovery << ", incr = " << incr << ", ";
-    }
-    if (incr < lrint((increment * ratio) / 2)) {
-        incr = lrint((increment * ratio) / 2);
-    } else if (incr > lrint(increment * ratio * 2)) {
-        incr = lrint(increment * ratio * 2);
+    if (m_debugLevel > 1) {
+        std::cerr << "StretchCalculator::calculateSingle: returning isTransient = "
+                  << isTransient << ", outIncrement = " << outIncrement
+                  << std::endl;
     }
 
-    double divdiff = (increment * ratio) - incr;
-
-    if (m_debugLevel > 2 || (m_debugLevel > 1 && m_divergence != 0)) {
-        std::cerr << "divdiff = " << divdiff << std::endl;
+    m_inFrameCounter += inIncrement;
+    m_outFrameCounter += outIncrement * effectivePitchRatio;
+    
+    if (isTransient) {
+        return -outIncrement;
+    } else {
+        return outIncrement;
     }
-
-    double prevDivergence = m_divergence;
-    m_divergence -= divdiff;
-    if ((prevDivergence < 0 && m_divergence > 0) ||
-        (prevDivergence > 0 && m_divergence < 0)) {
-        m_recovery = m_divergence / ((m_sampleRate / 10.0) / increment);
-    }
-
-    return incr;
 }
 
 void
 StretchCalculator::reset()
 {
     m_prevDf = 0;
-    m_divergence = 0;
-    m_recovery = 0;
     m_prevRatio = 1.0;
+    m_prevTimeRatio = 1.0;
+    m_inFrameCounter = 0;
+    m_frameCheckpoint = std::pair<int64_t, int64_t>(0, 0);
+    m_outFrameCounter = 0.0;
     m_transientAmnesty = 0;
     m_keyFrameMap.clear();
 }

          
M src/StretchCalculator.h +12 -5
@@ 68,8 68,12 @@ public:
      * If increment is non-zero, use it for the input increment for
      * this block in preference to m_increment.
      */
-    int calculateSingle(double ratio, float curveValue,
-                        size_t increment = 0);
+    int calculateSingle(double timeRatio,
+                        double effectivePitchRatio,
+                        float curveValue,
+                        size_t increment,
+                        size_t analysisWindowSize,
+                        size_t synthesisWindowSize);
 
     void setUseHardPeaks(bool use) { m_useHardPeaks = use; }
 

          
@@ 104,12 108,15 @@ protected:
     size_t m_sampleRate;
     size_t m_increment;
     float m_prevDf;
-    double m_divergence;
-    float m_recovery;
-    float m_prevRatio;
+    double m_prevRatio;
+    double m_prevTimeRatio;
     int m_transientAmnesty; // only in RT mode; handled differently offline
     int m_debugLevel;
     bool m_useHardPeaks;
+    int64_t m_inFrameCounter;
+    std::pair<int64_t, int64_t> m_frameCheckpoint;
+    int64_t expectedOutFrame(int64_t inFrame, double timeRatio);
+    double m_outFrameCounter;
 
     std::map<size_t, size_t> m_keyFrameMap;
     std::vector<Peak> m_peaks;

          
M src/StretcherChannelData.h +4 -5
@@ 27,8 27,7 @@ 
 #include "StretcherImpl.h"
 
 #include <set>
-
-//#define EXPERIMENT 1
+#include <atomic>
 
 namespace RubberBand
 {

          
@@ 124,11 123,11 @@ public:
 
     size_t chunkCount;
     size_t inCount;
-    long inputSize; // set only after known (when data ended); -1 previously
+    std::atomic<int64_t> inputSize; // set only after known (when data ended); -1 previously
     size_t outCount;
 
-    bool draining;
-    bool outputComplete;
+    std::atomic<bool> draining;
+    std::atomic<bool> outputComplete;
 
     FFT *fft;
     std::map<size_t, FFT *> ffts;

          
M src/StretcherImpl.cpp +41 -11
@@ 544,8 544,8 @@ RubberBandStretcher::Impl::calculateSize
     // ratio) for any chunk.
 
     if (m_debugLevel > 0) {
-        cerr << "configure: time ratio = " << m_timeRatio << ", pitch scale = " << m_pitchScale << ", effective ratio = " << getEffectiveRatio() << endl;
-        cerr << "configure: analysis window size = " << m_aWindowSize << ", synthesis window size = " << m_sWindowSize << ", fft size = " << m_fftSize << ", increment = " << m_increment << " (approx output increment = " << int(lrint(m_increment * getEffectiveRatio())) << ")" << endl;
+        cerr << "calculateSizes: time ratio = " << m_timeRatio << ", pitch scale = " << m_pitchScale << ", effective ratio = " << getEffectiveRatio() << endl;
+        cerr << "calculateSizes: analysis window size = " << m_aWindowSize << ", synthesis window size = " << m_sWindowSize << ", fft size = " << m_fftSize << ", increment = " << m_increment << " (approx output increment = " << int(lrint(m_increment * getEffectiveRatio())) << ")" << endl;
     }
 
     if (std::max(m_aWindowSize, m_sWindowSize) > m_maxProcessSize) {

          
@@ 575,15 575,17 @@ RubberBandStretcher::Impl::calculateSize
     }
 
     if (m_debugLevel > 0) {
-        cerr << "configure: outbuf size = " << m_outbufSize << endl;
+        cerr << "calculateSizes: outbuf size = " << m_outbufSize << endl;
     }
 }
 
 void
 RubberBandStretcher::Impl::configure()
 {
-//    std::cerr << "configure[" << this << "]: realtime = " << m_realtime << ", pitch scale = "
-//              << m_pitchScale << ", channels = " << m_channels << std::endl;
+    if (m_debugLevel > 0) {
+        std::cerr << "configure[" << this << "]: realtime = " << m_realtime << ", pitch scale = "
+                  << m_pitchScale << ", channels = " << m_channels << std::endl;
+    }
 
     size_t prevFftSize = m_fftSize;
     size_t prevAWindowSize = m_aWindowSize;

          
@@ 674,8 676,18 @@ RubberBandStretcher::Impl::configure()
 
             Resampler::Parameters params;
             params.quality = Resampler::FastestTolerable;
+
+            if (m_realtime) {
+                params.dynamism = Resampler::RatioOftenChanging;
+                params.ratioChange = Resampler::SmoothRatioChange;
+            } else {
+                // ratio can't be changed in offline mode
+                params.dynamism = Resampler::RatioMostlyFixed;
+                params.ratioChange = Resampler::SuddenRatioChange;
+            }
+            
             params.maxBufferSize = 4096 * 16;
-            params.debugLevel = m_debugLevel;
+            params.debugLevel = (m_debugLevel > 0 ? m_debugLevel-1 : 0);
             
             m_channelData[c]->resampler = new Resampler(params, 1);
 

          
@@ 734,7 746,7 @@ RubberBandStretcher::Impl::configure()
 
     if (!m_realtime) {
         if (m_debugLevel > 1) {
-            cerr << "Not real time mode: prefilling" << endl;
+            cerr << "Not real time mode: prefilling with " << m_aWindowSize/2 << " samples" << endl;
         }
         for (size_t c = 0; c < m_channels; ++c) {
             m_channelData[c]->reset();

          
@@ 767,6 779,8 @@ RubberBandStretcher::Impl::reconfigure()
 
     calculateSizes();
 
+    bool somethingChanged = false;
+    
     // There are various allocations in this function, but they should
     // never happen in normal use -- they just recover from the case
     // where not all of the things we need were correctly created when

          
@@ 801,12 815,15 @@ RubberBandStretcher::Impl::reconfigure()
             m_channelData[c]->setSizes(std::max(m_aWindowSize, m_sWindowSize),
                                        m_fftSize);
         }
+
+        somethingChanged = true;
     }
 
     if (m_outbufSize != prevOutbufSize) {
         for (size_t c = 0; c < m_channels; ++c) {
             m_channelData[c]->setOutbufSize(m_outbufSize);
         }
+        somethingChanged = true;
     }
 
     if (m_pitchScale != 1.0) {

          
@@ 818,8 835,10 @@ RubberBandStretcher::Impl::reconfigure()
 
             Resampler::Parameters params;
             params.quality = Resampler::FastestTolerable;
+            params.dynamism = Resampler::RatioOftenChanging;
+            params.ratioChange = Resampler::SmoothRatioChange;
             params.maxBufferSize = m_sWindowSize;
-            params.debugLevel = m_debugLevel;
+            params.debugLevel = (m_debugLevel > 0 ? m_debugLevel-1 : 0);
             
             m_channelData[c]->resampler = new Resampler(params, 1);
 

          
@@ 827,6 846,8 @@ RubberBandStretcher::Impl::reconfigure()
                 lrintf(ceil((m_increment * m_timeRatio * 2) / m_pitchScale));
             if (rbs < m_increment * 16) rbs = m_increment * 16;
             m_channelData[c]->setResampleBufSize(rbs);
+
+            somethingChanged = true;
         }
     }
 

          
@@ 836,6 857,15 @@ RubberBandStretcher::Impl::reconfigure()
         if (m_stretchAudioCurve) {
             m_stretchAudioCurve->setFftSize(m_fftSize);
         }
+        somethingChanged = true;
+    }
+
+    if (m_debugLevel > 0) {
+        if (somethingChanged) {
+            std::cerr << "reconfigure: at least one parameter changed" << std::endl;
+        } else {
+            std::cerr << "reconfigure: nothing changed" << std::endl;
+        }
     }
 }
 

          
@@ 843,7 873,7 @@ size_t
 RubberBandStretcher::Impl::getLatency() const
 {
     if (!m_realtime) return 0;
-    return int((m_aWindowSize/2) / m_pitchScale + 1);
+    return lrint((m_aWindowSize/2) / m_pitchScale);
 }
 
 void

          
@@ 1347,12 1377,12 @@ RubberBandStretcher::Impl::process(const
         }
 #endif
 
-        if (m_debugLevel > 2) {
+        if (m_debugLevel > 1) {
             if (!allConsumed) cerr << "process looping" << endl;
         }
     }
 
-    if (m_debugLevel > 2) {
+    if (m_debugLevel > 1) {
         cerr << "process returning" << endl;
     }
 

          
M src/StretcherImpl.h +1 -1
@@ 24,7 24,7 @@ 
 #ifndef RUBBERBAND_STRETCHERIMPL_H
 #define RUBBERBAND_STRETCHERIMPL_H
 
-#include "rubberband/RubberBandStretcher.h"
+#include "../rubberband/RubberBandStretcher.h"
 
 #include "dsp/Window.h"
 #include "dsp/SincWindow.h"

          
M src/StretcherProcess.cpp +14 -4
@@ 189,6 189,8 @@ RubberBandStretcher::Impl::consumeChanne
 
     if (resampling) {
 
+        Profiler profiler2("RubberBandStretcher::Impl::resample");
+        
         toWrite = int(ceil(samples / m_pitchScale));
         if (writable < toWrite) {
             samples = int(floor(writable * m_pitchScale));

          
@@ 282,7 284,7 @@ RubberBandStretcher::Impl::processChunks
     while (!last) {
 
         if (!testInbufReadSpace(c)) {
-            if (m_debugLevel > 2) {
+            if (m_debugLevel > 1) {
                 cerr << "processChunks: out of input" << endl;
             }
             break;

          
@@ 347,7 349,7 @@ RubberBandStretcher::Impl::processOneChu
 
     for (size_t c = 0; c < m_channels; ++c) {
         if (!testInbufReadSpace(c)) {
-            if (m_debugLevel > 2) {
+            if (m_debugLevel > 1) {
                 cerr << "processOneChunk: out of input" << endl;
             }
             return false;

          
@@ 402,7 404,7 @@ RubberBandStretcher::Impl::testInbufRead
             if (!m_threaded) {
 #endif
                 if (m_debugLevel > 1) {
-                    cerr << "WARNING: RubberBandStretcher: read space < chunk size ("
+                    cerr << "Note: RubberBandStretcher: read space < chunk size ("
                          << inbuf.getReadSpace() << " < " << m_aWindowSize
                          << ") when not all input written, on processChunks for channel " << c << endl;
                 }

          
@@ 616,8 618,14 @@ RubberBandStretcher::Impl::calculateIncr
         }
     }
 
+    double effectivePitchRatio = 1.0 / m_pitchScale;
+    if (cd.resampler) {
+        effectivePitchRatio = cd.resampler->getEffectiveRatio(effectivePitchRatio);
+    }
+    
     int incr = m_stretchCalculator->calculateSingle
-        (getEffectiveRatio(), df, m_increment);
+        (m_timeRatio, effectivePitchRatio, df, m_increment,
+         m_aWindowSize, m_sWindowSize);
 
     if (m_lastProcessPhaseResetDf.getWriteSpace() > 0) {
         m_lastProcessPhaseResetDf.write(&df, 1);

          
@@ 1071,6 1079,8 @@ RubberBandStretcher::Impl::writeChunk(si
         (m_pitchScale != 1.0 || m_options & OptionPitchHighConsistency) &&
         cd.resampler) {
 
+        Profiler profiler2("RubberBandStretcher::Impl::resample");
+
         size_t reqSize = int(ceil(si / m_pitchScale));
         if (reqSize > cd.resamplebufSize) {
             // This shouldn't normally happen -- the buffer is

          
M src/audiocurves/CompoundAudioCurve.cpp +1 -1
@@ 23,7 23,7 @@ 
 
 #include "CompoundAudioCurve.h"
 
-#include "dsp/MovingMedian.h"
+#include "../dsp/MovingMedian.h"
 
 #include <iostream>
 

          
M src/audiocurves/CompoundAudioCurve.h +1 -2
@@ 24,10 24,9 @@ 
 #ifndef RUBBERBAND_COMPOUND_AUDIO_CURVE_H
 #define RUBBERBAND_COMPOUND_AUDIO_CURVE_H
 
-#include "dsp/AudioCurveCalculator.h"
 #include "PercussiveAudioCurve.h"
 #include "HighFrequencyAudioCurve.h"
-#include "dsp/SampleFilter.h"
+#include "../dsp/SampleFilter.h"
 
 namespace RubberBand
 {

          
M src/audiocurves/ConstantAudioCurve.h +1 -1
@@ 24,7 24,7 @@ 
 #ifndef RUBBERBAND_CONSTANT_AUDIO_CURVE_H
 #define RUBBERBAND_CONSTANT_AUDIO_CURVE_H
 
-#include "dsp/AudioCurveCalculator.h"
+#include "../dsp/AudioCurveCalculator.h"
 
 namespace RubberBand
 {

          
M src/audiocurves/HighFrequencyAudioCurve.h +1 -1
@@ 24,7 24,7 @@ 
 #ifndef RUBBERBAND_HIGHFREQUENCY_AUDIO_CURVE_H
 #define RUBBERBAND_HIGHFREQUENCY_AUDIO_CURVE_H
 
-#include "dsp/AudioCurveCalculator.h"
+#include "../dsp/AudioCurveCalculator.h"
 
 namespace RubberBand
 {

          
M src/audiocurves/PercussiveAudioCurve.cpp +2 -2
@@ 23,8 23,8 @@ 
 
 #include "PercussiveAudioCurve.h"
 
-#include "system/Allocators.h"
-#include "system/VectorOps.h"
+#include "../system/Allocators.h"
+#include "../system/VectorOps.h"
 
 #include <cmath>
 #include <iostream>

          
M src/audiocurves/PercussiveAudioCurve.h +1 -1
@@ 24,7 24,7 @@ 
 #ifndef RUBBERBAND_PERCUSSIVE_AUDIO_CURVE_H
 #define RUBBERBAND_PERCUSSIVE_AUDIO_CURVE_H
 
-#include "dsp/AudioCurveCalculator.h"
+#include "../dsp/AudioCurveCalculator.h"
 
 namespace RubberBand
 {

          
M src/audiocurves/SilentAudioCurve.h +1 -1
@@ 24,7 24,7 @@ 
 #ifndef RUBBERBAND_SILENT_AUDIO_CURVE_H
 #define RUBBERBAND_SILENT_AUDIO_CURVE_H
 
-#include "dsp/AudioCurveCalculator.h"
+#include "../dsp/AudioCurveCalculator.h"
 
 namespace RubberBand
 {

          
M src/audiocurves/SpectralDifferenceAudioCurve.cpp +2 -2
@@ 23,8 23,8 @@ 
 
 #include "SpectralDifferenceAudioCurve.h"
 
-#include "system/Allocators.h"
-#include "system/VectorOps.h"
+#include "../system/Allocators.h"
+#include "../system/VectorOps.h"
 
 namespace RubberBand
 {

          
M src/audiocurves/SpectralDifferenceAudioCurve.h +2 -2
@@ 24,8 24,8 @@ 
 #ifndef RUBBERBAND_SPECTRALDIFFERENCE_AUDIO_CURVE_H
 #define RUBBERBAND_SPECTRALDIFFERENCE_AUDIO_CURVE_H
 
-#include "dsp/AudioCurveCalculator.h"
-#include "dsp/Window.h"
+#include "../dsp/AudioCurveCalculator.h"
+#include "../dsp/Window.h"
 
 namespace RubberBand
 {

          
M src/base/Profiler.cpp +1 -1
@@ 23,7 23,7 @@ 
 
 #include "Profiler.h"
 
-#include "system/Thread.h"
+#include "../system/Thread.h"
 
 #include <algorithm>
 #include <set>

          
M src/base/Profiler.h +1 -1
@@ 42,7 42,7 @@ 
 #ifdef PROFILE_CLOCKS
 #include <time.h>
 #else
-#include "system/sysutils.h"
+#include "../system/sysutils.h"
 #ifndef _WIN32
 #include <sys/time.h>
 #endif

          
M src/base/RingBuffer.h +13 -14
@@ 28,11 28,13 @@ 
 
 //#define DEBUG_RINGBUFFER 1
 
-#include "system/sysutils.h"
-#include "system/Allocators.h"
+#include "../system/sysutils.h"
+#include "../system/Allocators.h"
 
 #include <iostream>
 
+#include <atomic>
+
 namespace RubberBand {
 
 /**

          
@@ 174,11 176,11 @@ public:
     int zero(int n);
 
 protected:
-    T *const R__ m_buffer;
-    int          m_writer;
-    int          m_reader;
-    const int    m_size;
-    bool         m_mlocked;
+    T *const R__      m_buffer;
+    std::atomic<int>  m_writer;
+    std::atomic<int>  m_reader;
+    const int         m_size;
+    bool              m_mlocked;
 
     int readSpaceFor(int w, int r) const {
         int space;

          
@@ 243,7 245,8 @@ RingBuffer<T> *
 RingBuffer<T>::resized(int newSize) const
 {
     RingBuffer<T> *newBuffer = new RingBuffer<T>(newSize);
-
+    
+    MBARRIER();
     int w = m_writer;
     int r = m_reader;
 

          
@@ 273,7 276,8 @@ RingBuffer<T>::reset()
     std::cerr << "RingBuffer<T>[" << this << "]::reset" << std::endl;
 #endif
 
-    m_reader = m_writer;
+    int r = m_reader;
+    m_writer = r;
 }
 
 template <typename T>

          
@@ 302,7 306,6 @@ RingBuffer<T>::read(S *const R__ destina
     if (n > available) {
 	std::cerr << "WARNING: RingBuffer::read: " << n << " requested, only "
                   << available << " available" << std::endl;
-//!!!        v_zero(destination + available, n - available);
 	n = available;
     }
     if (n == 0) return n;

          
@@ 320,7 323,6 @@ RingBuffer<T>::read(S *const R__ destina
     r += n;
     while (r >= m_size) r -= m_size;
 
-    MBARRIER();
     m_reader = r;
 
     return n;

          
@@ 355,7 357,6 @@ RingBuffer<T>::readAdding(S *const R__ d
     r += n;
     while (r >= m_size) r -= m_size;
 
-    MBARRIER();
     m_reader = r;
 
     return n;

          
@@ 377,7 378,6 @@ RingBuffer<T>::readOne()
     T value = m_buffer[r];
     if (++r == m_size) r = 0;
 
-    MBARRIER();
     m_reader = r;
 
     return value;

          
@@ 447,7 447,6 @@ RingBuffer<T>::skip(int n)
     r += n;
     while (r >= m_size) r -= m_size;
 
-    // No memory barrier required, because we didn't read any data
     m_reader = r;
 
     return n;

          
M src/base/Scavenger.h +3 -3
@@ 33,9 33,9 @@ 
 #include <sys/time.h>
 #endif
 
-#include "system/Thread.h"
-#include "system/sysutils.h"
-#include "system/Allocators.h"
+#include "../system/Thread.h"
+#include "../system/sysutils.h"
+#include "../system/Allocators.h"
 
 //#define DEBUG_SCAVENGER 1
 

          
M src/dsp/AudioCurveCalculator.h +1 -2
@@ 26,8 26,7 @@ 
 
 #include <sys/types.h>
 
-
-#include "system/sysutils.h"
+#include "../system/sysutils.h"
 
 namespace RubberBand 
 {

          
A => src/dsp/BQResampler.cpp +646 -0
@@ 0,0 1,646 @@ 
+//* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Rubber Band Library
+    An audio time-stretching and pitch-shifting library.
+    Copyright 2007-2021 Particular Programs Ltd.
+
+    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.
+
+    Alternatively, if you have a valid commercial licence for the
+    Rubber Band Library obtained by agreement with the copyright
+    holders, you may redistribute and/or modify it under the terms
+    described in that licence.
+
+    If you wish to distribute code using the Rubber Band Library
+    under terms other than those of the GNU General Public License,
+    you must obtain a valid commercial licence before doing so.
+*/
+
+#include "BQResampler.h"
+
+#include <cmath>
+
+#include <iostream>
+
+#include "../system/Allocators.h"
+#include "../system/VectorOps.h"
+
+#define BQ_R__ R__
+
+using std::vector;
+using std::cerr;
+using std::endl;
+using std::min;
+using std::max;
+
+namespace RubberBand {
+
+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/dsp/BQResampler.h +167 -0
@@ 0,0 1,167 @@ 
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Rubber Band Library
+    An audio time-stretching and pitch-shifting library.
+    Copyright 2007-2021 Particular Programs Ltd.
+
+    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.
+
+    Alternatively, if you have a valid commercial licence for the
+    Rubber Band Library obtained by agreement with the copyright
+    holders, you may redistribute and/or modify it under the terms
+    described in that licence.
+
+    If you wish to distribute code using the Rubber Band Library
+    under terms other than those of the GNU General Public License,
+    you must obtain a valid commercial licence before doing so.
+*/
+
+#ifndef BQ_BQRESAMPLER_H
+#define BQ_BQRESAMPLER_H
+
+#include <vector>
+
+#include "../system/Allocators.h"
+#include "../system/VectorOps.h"
+
+namespace RubberBand {
+
+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, RubberBand::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 &R__ prev_state) const;
+    
+    double reconstruct_one(state *s) const;
+
+    BQResampler &operator=(const BQResampler &); // not provided
+};
+
+}
+
+#endif

          
M src/dsp/FFT.cpp +5 -5
@@ 22,11 22,11 @@ 
 */
 
 #include "FFT.h"
-#include "system/Thread.h"
-#include "base/Profiler.h"
-#include "system/Allocators.h"
-#include "system/VectorOps.h"
-#include "system/VectorOpsComplex.h"
+#include "../system/Thread.h"
+#include "../base/Profiler.h"
+#include "../system/Allocators.h"
+#include "../system/VectorOps.h"
+#include "../system/VectorOpsComplex.h"
 
 // Define USE_FFTW_WISDOM if you are defining HAVE_FFTW3 and you want
 // to use FFTW_MEASURE mode with persistent wisdom files. This will

          
M src/dsp/FFT.h +1 -1
@@ 24,7 24,7 @@ 
 #ifndef RUBBERBAND_FFT_H
 #define RUBBERBAND_FFT_H
 
-#include "system/sysutils.h"
+#include "../system/sysutils.h"
 
 #include <string>
 #include <set>

          
M src/dsp/MovingMedian.h +1 -1
@@ 26,7 26,7 @@ 
 
 #include "SampleFilter.h"
 
-#include "system/Allocators.h"
+#include "../system/Allocators.h"
 
 #include <algorithm>
 

          
M src/dsp/Resampler.cpp +314 -121
@@ 1,4 1,4 @@ 
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
 
 /*
     Rubber Band Library

          
@@ 22,7 22,9 @@ 
 */
 
 #include "Resampler.h"
-#include "base/Profiler.h"
+
+#include "../system/Allocators.h"
+#include "../system/VectorOps.h"
 
 #include <cstdlib>
 #include <cmath>

          
@@ 30,9 32,6 @@ 
 #include <iostream>
 #include <algorithm>
 
-#include "system/Allocators.h"
-#include "system/VectorOps.h"
-
 #ifdef HAVE_IPP
 #include <ippversion.h>
 #if (IPP_VERSION_MAJOR < 7)

          
@@ 42,6 41,10 @@ 
 #endif
 #endif
 
+#ifdef HAVE_SAMPLERATE
+#define HAVE_LIBSAMPLERATE 1
+#endif
+
 #ifdef HAVE_LIBSAMPLERATE
 #include <samplerate.h>
 #endif

          
@@ 51,18 54,26 @@ 
 #endif
 
 #ifdef USE_SPEEX
-#include "speex/speex_resampler.h"
+#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
+
+#define BQ_R__ R__
 
 using namespace std;
 

          
@@ 73,21 84,22 @@ class Resampler::Impl
 public:
     virtual ~Impl() { }
     
-    virtual int resample(float *const R__ *const R__ out,
+    virtual int resample(float *const BQ_R__ *const BQ_R__ out,
                          int outcount,
-                         const float *const R__ *const R__ in, 
+                         const float *const BQ_R__ *const BQ_R__ in, 
                          int incount,
                          double ratio,
                          bool final) = 0;
     
-    virtual int resampleInterleaved(float *const R__ out,
+    virtual int resampleInterleaved(float *const BQ_R__ out,
                                     int outcount,
-                                    const float *const R__ in, 
+                                    const float *const BQ_R__ in, 
                                     int incount,
                                     double ratio,
                                     bool final) = 0;
 
     virtual int getChannelCount() const = 0;
+    virtual double getEffectiveRatio(double ratio) const = 0;
 
     virtual void reset() = 0;
 };

          
@@ 99,25 111,27 @@ 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();
 
-    int resample(float *const R__ *const R__ out,
+    int resample(float *const BQ_R__ *const BQ_R__ out,
                  int outcount,
-                 const float *const R__ *const R__ in,
+                 const float *const BQ_R__ *const BQ_R__ in,
                  int incount,
                  double ratio,
                  bool final);
 
-    int resampleInterleaved(float *const R__ out,
+    int resampleInterleaved(float *const BQ_R__ out,
                             int outcount,
-                            const float *const R__ in,
+                            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 ratio; }
 
     void reset();
 

          
@@ 144,6 158,7 @@ protected:
 };
 
 D_IPP::D_IPP(Resampler::Quality /* quality */,
+             Resampler::RatioChange /* ratioChange */,
              int channels, double initialSampleRate,
              int maxBufferSize, int debugLevel) :
     m_state(0),

          
@@ 152,7 167,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;

          
@@ 190,7 205,7 @@ D_IPP::D_IPP(Resampler::Quality /* quali
     setBufSize(maxBufferSize + m_history);
 
     if (m_debugLevel > 1) {
-        cerr << "D_IPP: bufsize = " << m_bufsize << ", window = " << m_window << ", nStep = " << nStep << ", history = " << m_history << endl;
+        cerr << "bufsize = " << m_bufsize << ", window = " << m_window << ", nStep = " << nStep << ", history = " << m_history << endl;
     }
 
     int specSize = 0;

          
@@ 214,18 229,13 @@ D_IPP::D_IPP(Resampler::Quality /* quali
                                       9.0f,
                                       m_state[c],
                                       hint);
-
-        if (m_debugLevel > 1) {
-            cerr << "D_IPP: Resampler state size = " << specSize << ", allocated at "
-                 << m_state[c] << endl;
-        }
         
         m_lastread[c] = m_history;
         m_time[c] = m_history;
     }
 
     if (m_debugLevel > 1) {
-        cerr << "D_IPP: Resampler init done" << endl;
+        cerr << "Resampler init done" << endl;
     }
 }
 

          
@@ 248,9 258,9 @@ D_IPP::setBufSize(int sz)
 {
     if (m_debugLevel > 1) {
         if (m_bufsize > 0) {
-            cerr << "D_IPP: resize bufsize " << m_bufsize << " -> ";
+            cerr << "resize bufsize " << m_bufsize << " -> ";
         } else {
-            cerr << "D_IPP: initialise bufsize to ";
+            cerr << "initialise bufsize to ";
         }
     }
 

          
@@ 263,13 273,13 @@ D_IPP::setBufSize(int sz)
     int n1 = m_bufsize + m_history + 2;
 
     if (m_debugLevel > 1) {
-        cerr << "D_IPP: inbuf allocating " << m_bufsize << " + " << m_history << " + 2 = " << n1 << endl;
+        cerr << "inbuf allocating " << m_bufsize << " + " << m_history << " + 2 = " << n1 << endl;
     }
 
     int n2 = (int)lrintf(ceil((m_bufsize - m_history) * m_factor + 2));
 
     if (m_debugLevel > 1) {
-        cerr << "D_IPP: outbuf allocating (" << m_bufsize << " - " << m_history << ") * " << m_factor << " + 2 = " << n2 << endl;
+        cerr << "outbuf allocating (" << m_bufsize << " - " << m_history << ") * " << m_factor << " + 2 = " << n2 << endl;
     }
 
     m_inbuf = reallocate_and_zero_extend_channels

          
@@ 277,30 287,15 @@ D_IPP::setBufSize(int sz)
 
     m_outbuf = reallocate_and_zero_extend_channels
         (m_outbuf, m_channels, m_outbufsz, m_channels, n2);
-    
+            
     m_inbufsz = n1;
     m_outbufsz = n2;
-
-    if (m_debugLevel > 2) {
-
-        cerr << "D_IPP: inbuf ptr = " << m_inbuf << ", channel inbufs ";
-        for (int c = 0; c < m_channels; ++c) {
-            cerr << m_inbuf[c] << " ";
-        }
-        cerr << "at " << m_inbufsz * sizeof(float) << " bytes each" << endl;
-
-        cerr << "D_IPP: outbuf ptr = " << m_outbuf << ", channel outbufs ";
-        for (int c = 0; c < m_channels; ++c) {
-            cerr << m_outbuf[c] << " ";
-        }
-        cerr << "at " << m_outbufsz * sizeof(float) << " bytes each" << endl;
-    }        
 }
 
 int
-D_IPP::resample(float *const R__ *const R__ out,
+D_IPP::resample(float *const BQ_R__ *const BQ_R__ out,
                 int outspace,
-                const float *const R__ *const R__ in,
+                const float *const BQ_R__ *const BQ_R__ in,
                 int incount,
                 double ratio,
                 bool final)

          
@@ 311,7 306,7 @@ D_IPP::resample(float *const R__ *const 
     }
 
     if (m_debugLevel > 2) {
-        cerr << "D_IPP: incount = " << incount << ", ratio = " << ratio << ", est space = " << lrintf(ceil(incount * ratio)) << ", outspace = " << outspace << ", final = " << final << endl;
+        cerr << "incount = " << incount << ", ratio = " << ratio << ", est space = " << lrintf(ceil(incount * ratio)) << ", outspace = " << outspace << ", final = " << final << endl;
     }
 
     for (int c = 0; c < m_channels; ++c) {

          
@@ 328,7 323,7 @@ D_IPP::resample(float *const R__ *const 
     }
 
     if (m_debugLevel > 2) {
-        cerr << "D_IPP: lastread advanced to " << m_lastread[0] << endl;
+        cerr << "lastread advanced to " << m_lastread[0] << endl;
     }
 
     int got = doResample(outspace, ratio, final);

          
@@ 341,9 336,9 @@ D_IPP::resample(float *const R__ *const 
 }
 
 int
-D_IPP::resampleInterleaved(float *const R__ out,
+D_IPP::resampleInterleaved(float *const BQ_R__ out,
                            int outspace,
-                           const float *const R__ in,
+                           const float *const BQ_R__ in,
                            int incount,
                            double ratio,
                            bool final)

          
@@ 354,7 349,7 @@ D_IPP::resampleInterleaved(float *const 
     }
 
     if (m_debugLevel > 2) {
-        cerr << "D_IPP: incount = " << incount << ", ratio = " << ratio << ", est space = " << lrintf(ceil(incount * ratio)) << ", outspace = " << outspace << ", final = " << final << endl;
+        cerr << "incount = " << incount << ", ratio = " << ratio << ", est space = " << lrintf(ceil(incount * ratio)) << ", outspace = " << outspace << ", final = " << final << endl;
     }
 
     for (int c = 0; c < m_channels; ++c) {

          
@@ 371,7 366,7 @@ D_IPP::resampleInterleaved(float *const 
     }
 
     if (m_debugLevel > 2) {
-        cerr << "D_IPP: lastread advanced to " << m_lastread[0] << " after injection of "
+        cerr << "lastread advanced to " << m_lastread[0] << " after injection of "
              << incount << " samples" << endl;
     }
 

          
@@ 392,20 387,20 @@ D_IPP::doResample(int outspace, double r
         int n = m_lastread[c] - m_history - int(m_time[c]);
 
         if (c == 0 && m_debugLevel > 2) {
-            cerr << "D_IPP: at start, lastread = " << m_lastread[c] << ", history = "
+            cerr << "at start, lastread = " << m_lastread[c] << ", history = "
                  << m_history << ", time = " << m_time[c] << ", therefore n = "
                  << n << endl;
         }
 
         if (n <= 0) {
             if (c == 0 && m_debugLevel > 1) {
-                cerr << "D_IPP: not enough input samples to do anything" << endl;
+                cerr << "not enough input samples to do anything" << endl;
             }
             continue;
         }
         
         if (c == 0 && m_debugLevel > 2) {
-            cerr << "D_IPP: before resample call, time = " << m_time[c] << endl;
+            cerr << "before resample call, time = " << m_time[c] << endl;
         }
 
         // We're committed to not overrunning outspace, so we need to

          
@@ 414,7 409,7 @@ D_IPP::doResample(int outspace, double r
         int limit = int(floor(outspace / ratio));
         if (n > limit) {
             if (c == 0 && m_debugLevel > 1) {
-                cerr << "D_IPP: trimming input samples from " << n << " to " << limit
+                cerr << "trimming input samples from " << n << " to " << limit
                      << " to avoid overrunning " << outspace << " at output"
                      << endl;
             }

          
@@ 431,26 426,26 @@ D_IPP::doResample(int outspace, double r
                                   m_state[c]);
 
         int t = int(floor(m_time[c]));
-
+        
         int moveFrom = t - m_history;
-
+        
         if (c == 0 && m_debugLevel > 2) {
-            cerr << "D_IPP: converted " << n << " samples to " << outcount
+            cerr << "converted " << n << " samples to " << outcount
                  << " (nb outbufsz = " << m_outbufsz
                  << "), time advanced to " << m_time[c] << endl;
-            cerr << "D_IPP: rounding time to " << t << ", lastread = "
+            cerr << "rounding time to " << t << ", lastread = "
                  << m_lastread[c] << ", history = " << m_history << endl;
-            cerr << "D_IPP: will move " << m_lastread[c] - moveFrom
+            cerr << "will move " << m_lastread[c] - moveFrom
                  << " unconverted samples back from index " << moveFrom
                  << " to 0" << endl;
         }
-        
+
         if (moveFrom >= m_lastread[c]) {
 
             moveFrom = m_lastread[c];
 
             if (c == 0 && m_debugLevel > 2) {
-                cerr << "D_IPP: number of samples to move is <= 0, "
+                cerr << "number of samples to move is <= 0, "
                      << "not actually moving any" << endl;
             }
         } else {

          
@@ 464,7 459,7 @@ D_IPP::doResample(int outspace, double r
         m_time[c] -= moveFrom;
 
         if (c == 0 && m_debugLevel > 2) {
-            cerr << "D_IPP: lastread reduced to " << m_lastread[c]
+            cerr << "lastread reduced to " << m_lastread[c]
                  << ", time reduced to " << m_time[c]
                  << endl;
         }

          
@@ 483,7 478,7 @@ D_IPP::doResample(int outspace, double r
             int additionalcount = 0;
 
             if (c == 0 && m_debugLevel > 2) {
-                cerr << "D_IPP: final call, padding input with " << m_history
+                cerr << "final call, padding input with " << m_history
                      << " zeros (symmetrical with m_history)" << endl;
             }
             

          
@@ 492,14 487,14 @@ D_IPP::doResample(int outspace, double r
             }
 
             if (c == 0 && m_debugLevel > 2) {
-                cerr << "D_IPP: before resample call, time = " << m_time[c] << endl;
+                cerr << "before resample call, time = " << m_time[c] << endl;
             }
 
             int nAdditional = m_lastread[c] - int(m_time[c]);
 
             if (n + nAdditional > limit) {
                 if (c == 0 && m_debugLevel > 1) {
-                    cerr << "D_IPP: trimming final input samples from " << nAdditional
+                    cerr << "trimming final input samples from " << nAdditional
                          << " to " << (limit - n)
                          << " to avoid overrunning " << outspace << " at output"
                          << endl;

          
@@ 517,9 512,9 @@ D_IPP::doResample(int outspace, double r
                                       m_state[c]);
         
             if (c == 0 && m_debugLevel > 2) {
-                cerr << "D_IPP: converted " << n << " samples to " << additionalcount
+                cerr << "converted " << n << " samples to " << additionalcount
                      << ", time advanced to " << m_time[c] << endl;
-                cerr << "D_IPP: outcount = " << outcount << ", additionalcount = " << additionalcount << ", sum " << outcount + additionalcount << endl;
+                cerr << "outcount = " << outcount << ", additionalcount = " << additionalcount << ", sum " << outcount + additionalcount << endl;
             }
 
             if (c == 0) {

          
@@ 529,7 524,7 @@ D_IPP::doResample(int outspace, double r
     }
 
     if (m_debugLevel > 2) {
-        cerr << "D_IPP: returning " << outcount << " samples" << endl;
+        cerr << "returning " << outcount << " samples" << endl;
     }
     
     return outcount;

          
@@ 548,25 543,27 @@ 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();
 
-    int resample(float *const R__ *const R__ out,
+    int resample(float *const BQ_R__ *const BQ_R__ out,
                  int outcount,
-                 const float *const R__ *const R__ in,
+                 const float *const BQ_R__ *const BQ_R__ in,
                  int incount,
                  double ratio,
                  bool final);
 
-    int resampleInterleaved(float *const R__ out,
+    int resampleInterleaved(float *const BQ_R__ out,
                             int outcount,
-                            const float *const R__ in,
+                            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 ratio; }
 
     void reset();
 

          
@@ 579,11 576,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),

          
@@ 592,25 590,41 @@ 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"
-                  << endl;
+        cerr << "Resampler::Resampler: using implementation: libsamplerate"
+             << endl;
     }
 
+    if (channels < 1) {
+        cerr << "Resampler::Resampler: unable to create resampler: invalid channel count " << channels << " supplied" << endl;
+#ifdef NO_EXCEPTIONS
+        throw Resampler::ImplementationError;
+#endif
+        return;
+    }
+    
     int err = 0;
     m_src = src_new(quality == Resampler::Best ? SRC_SINC_BEST_QUALITY :
-                    quality == Resampler::Fastest ? SRC_LINEAR :
-                    SRC_SINC_FASTEST,
+                    quality == Resampler::Fastest ? SRC_SINC_FASTEST :
+                    SRC_SINC_MEDIUM_QUALITY,
                     channels, &err);
 
     if (err) {
         cerr << "Resampler::Resampler: failed to create libsamplerate resampler: " 
-                  << src_strerror(err) << endl;
+             << src_strerror(err) << endl;
 #ifndef NO_EXCEPTIONS
         throw Resampler::ImplementationError;
 #endif
+        return;
+    } else if (!m_src) {
+        cerr << "Resampler::Resampler: failed to create libsamplerate resampler, but no error reported?" << endl;
+#ifndef NO_EXCEPTIONS
+        throw Resampler::ImplementationError;
+#endif
+        return;
     }
 
     if (maxBufferSize > 0 && m_channels > 1) {

          
@@ 631,9 645,9 @@ D_SRC::~D_SRC()
 }
 
 int
-D_SRC::resample(float *const R__ *const R__ out,
+D_SRC::resample(float *const BQ_R__ *const BQ_R__ out,
                 int outcount,
-                const float *const R__ *const R__ in,
+                const float *const BQ_R__ *const BQ_R__ in,
                 int incount,
                 double ratio,
                 bool final)

          
@@ 661,15 675,15 @@ D_SRC::resample(float *const R__ *const 
 }
 
 int
-D_SRC::resampleInterleaved(float *const R__ out,
+D_SRC::resampleInterleaved(float *const BQ_R__ out,
                            int outcount,
-                           const float *const R__ in,
+                           const float *const BQ_R__ in,
                            int incount,
                            double ratio,
                            bool final)
 {
     SRC_DATA data;
-        
+
     // libsamplerate smooths the filter change over the duration of
     // the processing block to avoid artifacts due to sudden changes,
     // and it uses outcount to determine how long to smooth the change

          
@@ 682,7 696,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);

          
@@ 724,10 738,9 @@ D_SRC::resampleInterleaved(float *const 
 
     data.input_frames = incount;
     data.output_frames = outcount;
-    
     data.src_ratio = ratio;
     data.end_of_input = (final ? 1 : 0);
-    
+
     int err = src_process(m_src, &data);
 
     if (err) {

          
@@ 737,7 750,7 @@ D_SRC::resampleInterleaved(float *const 
         throw Resampler::ImplementationError;
 #endif
     }
-    
+
     return (int)data.output_frames_gen;
 }
 

          
@@ 755,25 768,27 @@ 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();
 
-    int resample(float *const R__ *const R__ out,
+    int resample(float *const BQ_R__ *const BQ_R__ out,
                  int outcount,
-                 const float *const R__ *const R__ in,
+                 const float *const BQ_R__ *const BQ_R__ in,
                  int incount,
                  double ratio,
                  bool final);
 
-    int resampleInterleaved(float *const R__ out,
+    int resampleInterleaved(float *const BQ_R__ out,
                             int outcount,
-                            const float *const R__ in,
+                            const float *const BQ_R__ in,
                             int incount,
                             double ratio,
                             bool final);
 
     int getChannelCount() const { return m_channels; }
+    double getEffectiveRatio(double ratio) const { return ratio; }
 
     void reset();
 

          
@@ 799,7 814,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;
     }
 

          
@@ 836,9 851,9 @@ D_Resample::~D_Resample()
 }
 
 int
-D_Resample::resample(float *const R__ *const R__ out,
+D_Resample::resample(float *const BQ_R__ *const BQ_R__ out,
                      int outcount,
-                     const float *const R__ *const R__ in,
+                     const float *const BQ_R__ *const BQ_R__ in,
                      int incount,
                      double ratio,
                      bool final)

          
@@ 895,9 910,9 @@ D_Resample::resample(float *const R__ *c
 }
 
 int
-D_Resample::resampleInterleaved(float *const R__ out,
+D_Resample::resampleInterleaved(float *const BQ_R__ out,
                                 int outcount,
-                                const float *const R__ in,
+                                const float *const BQ_R__ in,
                                 int incount,
                                 double ratio,
                                 bool final)

          
@@ 937,30 952,186 @@ 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();
 
-    int resample(float *const R__ *const R__ out,
+    int resample(float *const BQ_R__ *const BQ_R__ out,
                  int outcount,
-                 const float *const R__ *const R__ in,
+                 const float *const BQ_R__ *const BQ_R__ in,
                  int incount,
                  double ratio,
                  bool final);
 
-    int resampleInterleaved(float *const R__ out,
+    int resampleInterleaved(float *const BQ_R__ out,
                             int outcount,
-                            const float *const R__ in,
+                            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 ratio; }
 
     void reset();
 

          
@@ 982,7 1153,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),

          
@@ 1000,7 1171,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;
     }
 

          
@@ 1093,9 1264,9 @@ D_Speex::setRatio(double ratio)
 }
 
 int
-D_Speex::resample(float *const R__ *const R__ out,
+D_Speex::resample(float *const BQ_R__ *const BQ_R__ out,
                   int outcount,
-                  const float *const R__ *const R__ in,
+                  const float *const BQ_R__ *const BQ_R__ in,
                   int incount,
                   double ratio,
                   bool final)

          
@@ 1136,9 1307,9 @@ D_Speex::resample(float *const R__ *cons
 }
 
 int
-D_Speex::resampleInterleaved(float *const R__ out,
+D_Speex::resampleInterleaved(float *const BQ_R__ out,
                              int outcount,
-                             const float *const R__ in,
+                             const float *const BQ_R__ in,
                              int incount,
                              double ratio,
                              bool final)

          
@@ 1236,6 1407,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

          
@@ 1251,6 1425,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

          
@@ 1266,6 1443,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

          
@@ 1281,7 1461,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

          
@@ 1293,7 1473,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

          
@@ 1305,7 1485,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

          
@@ 1317,7 1497,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

          
@@ 1325,6 1505,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) {

          
@@ 1340,26 1529,24 @@ Resampler::~Resampler()
 }
 
 int 
-Resampler::resample(float *const R__ *const R__ out,
+Resampler::resample(float *const BQ_R__ *const BQ_R__ out,
                     int outcount,
-                    const float *const R__ *const R__ in,
+                    const float *const BQ_R__ *const BQ_R__ in,
                     int incount,
                     double ratio,
                     bool final)
 {
-    Profiler profiler("Resampler::resample");
     return d->resample(out, outcount, in, incount, ratio, final);
 }
 
 int 
-Resampler::resampleInterleaved(float *const R__ out,
+Resampler::resampleInterleaved(float *const BQ_R__ out,
                                int outcount,
-                               const float *const R__ in,
+                               const float *const BQ_R__ in,
                                int incount,
                                double ratio,
                                bool final)
 {
-    Profiler profiler("Resampler::resampleInterleaved");
     return d->resampleInterleaved(out, outcount, in, incount, ratio, final);
 }
 

          
@@ 1369,6 1556,12 @@ Resampler::getChannelCount() const
     return d->getChannelCount();
 }
 
+double
+Resampler::getEffectiveRatio(double ratio) const
+{
+    return d->getEffectiveRatio(ratio);
+}
+
 void
 Resampler::reset()
 {

          
M src/dsp/Resampler.h +40 -2
@@ 1,4 1,4 @@ 
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
+//* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
 
 /*
     Rubber Band Library

          
@@ 24,7 24,7 @@ 
 #ifndef RUBBERBAND_RESAMPLER_H
 #define RUBBERBAND_RESAMPLER_H
 
-#include "system/sysutils.h"
+#include "../system/sysutils.h"
 
 namespace RubberBand {
 

          
@@ 32,6 32,9 @@ class Resampler
 {
 public:
     enum Quality { Best, FastestTolerable, Fastest };
+    enum Dynamism { RatioOftenChanging, RatioMostlyFixed };
+    enum RatioChange { SmoothRatioChange, SuddenRatioChange };
+    
     enum Exception { ImplementationError };
 
     struct Parameters {

          
@@ 41,6 44,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

          
@@ 67,6 86,8 @@ public:
 
         Parameters() :
             quality(FastestTolerable),
+            dynamism(RatioMostlyFixed),
+            ratioChange(SmoothRatioChange),
             initialSampleRate(44100),
             maxBufferSize(0),
             debugLevel(0) { }

          
@@ 125,8 146,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 src/dsp/SincWindow.h +3 -3
@@ 29,9 29,9 @@ 
 #include <cstdlib>
 #include <map>
 
-#include "system/sysutils.h"
-#include "system/VectorOps.h"
-#include "system/Allocators.h"
+#include "../system/sysutils.h"
+#include "../system/VectorOps.h"
+#include "../system/Allocators.h"
 
 namespace RubberBand {
 

          
M src/dsp/Window.h +3 -3
@@ 28,9 28,9 @@ 
 #include <cstdlib>
 #include <map>
 
-#include "system/sysutils.h"
-#include "system/VectorOps.h"
-#include "system/Allocators.h"
+#include "../system/sysutils.h"
+#include "../system/VectorOps.h"
+#include "../system/Allocators.h"
 
 namespace RubberBand {
 

          
M src/rubberband-c.cpp +2 -2
@@ 21,8 21,8 @@ 
     you must obtain a valid commercial licence before doing so.
 */
 
-#include "rubberband/rubberband-c.h"
-#include "rubberband/RubberBandStretcher.h"
+#include "../rubberband/rubberband-c.h"
+#include "../rubberband/RubberBandStretcher.h"
 
 struct RubberBandState_
 {

          
M src/system/Allocators.h +92 -0
@@ 29,6 29,8 @@ 
 #include <new> // for std::bad_alloc
 #include <stdlib.h>
 
+#include <stdexcept>
+
 #ifndef HAVE_POSIX_MEMALIGN
 #ifndef _WIN32
 #ifndef __APPLE__

          
@@ 309,6 311,96 @@ private:
     T *m_t;
 };
 
+/** Allocator for use with STL classes, e.g. vector, to ensure
+ *  alignment.  Based on example code by Stephan T. Lavavej.
+ *
+ *  e.g. std::vector<float, StlAllocator<float> > v;
+ */
+template <typename T>
+class StlAllocator
+{
+public:
+    typedef T *pointer;
+    typedef const T *const_pointer;
+    typedef T &reference;
+    typedef const T &const_reference;
+    typedef T value_type;
+    typedef size_t size_type;
+    typedef ptrdiff_t difference_type;
+
+    StlAllocator() { }
+    StlAllocator(const StlAllocator&) { }
+    template <typename U> StlAllocator(const StlAllocator<U>&) { }
+    ~StlAllocator() { }
+
+    T *
+    allocate(const size_t n) const {
+        if (n == 0) return 0;
+        if (n > max_size()) {
+#ifndef NO_EXCEPTIONS
+            throw std::length_error("Size overflow in StlAllocator::allocate()");
+#else
+            abort();
+#endif
+        }
+        return RubberBand::allocate<T>(n);
+    }
+
+    void
+    deallocate(T *const p, const size_t) const {
+        RubberBand::deallocate(p);
+    }
+
+    template <typename U>
+    T *
+    allocate(const size_t n, const U *) const {
+        return allocate(n);
+    }
+
+    T *
+    address(T &r) const {
+        return &r;
+    }
+
+    const T *
+    address(const T &s) const {
+        return &s;
+    }
+
+    size_t
+    max_size() const {
+        return (static_cast<size_t>(0) - static_cast<size_t>(1)) / sizeof(T);
+    }
+
+    template <typename U> struct rebind {
+        typedef StlAllocator<U> other;
+    };
+    
+    bool
+    operator==(const StlAllocator &) const {
+        return true;
+    }
+
+    bool
+    operator!=(const StlAllocator &) const {
+        return false;
+    }
+
+    void
+    construct(T *const p, const T &t) const {
+        void *const pv = static_cast<void *>(p);
+        new (pv) T(t);
+    }
+
+    void
+    destroy(T *const p) const {
+        p->~T();
+    }
+
+private:
+    StlAllocator& operator=(const StlAllocator&);
+};
+
 }
 
 #endif

          
M src/system/VectorOps.h +52 -0
@@ 492,6 492,58 @@ inline T v_sum(const T *const R__ src,
 }
 
 template<typename T>
+inline T v_multiply_and_sum(const T *const R__ src1,
+                            const T *const R__ src2,
+                            const int count)
+{
+    T result = T();
+    for (int i = 0; i < count; ++i) {
+        result += src1[i] * src2[i];
+    }
+    return result;
+}
+
+#if defined HAVE_IPP
+template<>
+inline float v_multiply_and_sum(const float *const R__ src1,
+                                const float *const R__ src2,
+                                const int count)
+{
+    float dp;
+    ippsDotProd_32f(src1, src2, count, &dp);
+    return dp;
+}
+template<>
+inline double v_multiply_and_sum(const double *const R__ src1,
+                                 const double *const R__ src2,
+                                 const int count)
+{
+    double dp;
+    ippsDotProd_64f(src1, src2, count, &dp);
+    return dp;
+}
+#elif defined HAVE_VDSP
+template<>
+inline float v_multiply_and_sum(const float *const R__ src1,
+                                const float *const R__ src2,
+                                const int count)
+{
+    float dp;
+    vDSP_dotpr(src1, 1, src2, 1, &dp, count);
+    return dp;
+}
+template<>
+inline double v_multiply_and_sum(const double *const R__ src1,
+                                 const double *const R__ src2,
+                                 const int count)
+{
+    double dp;
+    vDSP_dotprD(src1, 1, src2, 1, &dp, count);
+    return dp;
+}
+#endif
+
+template<typename T>
 inline void v_log(T *const R__ dst,
                   const int count)
 {

          
M src/system/sysutils.h +2 -2
@@ 135,8 135,8 @@ extern void system_memorybarrier();
 #include <dlfcn.h>
 #include <stdio.h>
 
-#define MLOCK(a,b)   ::mlock((char *)(a),(b))
-#define MUNLOCK(a,b) (::munlock((char *)(a),(b)) ? (::perror("munlock failed"), 0) : 0)
+#define MLOCK(a,b)   mlock((char *)(a),(b))
+#define MUNLOCK(a,b) (munlock((char *)(a),(b)) ? (perror("munlock failed"), 0) : 0)
 #define MUNLOCK_SAMPLEBLOCK(a) do { if (!(a).empty()) { const float &b = *(a).begin(); MUNLOCK(&b, (a).capacity() * sizeof(float)); } } while(0);
 
 #ifdef __APPLE__