diff --git a/.hgignore b/.hgignore index a10ed24..a335526 100644 --- a/.hgignore +++ b/.hgignore @@ -24,4 +24,5 @@ Release/ Debug/ build build_* +build-* UpgradeLog* diff --git a/Doxyfile b/Doxyfile index d3f3ab3..674841f 100644 --- a/Doxyfile +++ b/Doxyfile @@ -31,7 +31,7 @@ PROJECT_NAME = "Rubber Band Library" # 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. diff --git a/README.md b/README.md index c017f07..bc6865e 100644 --- a/README.md +++ b/README.md @@ -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 and/or modify it under the 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 README for detailed terms. * 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 README for detailed terms. 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 may benefit from different "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 ` 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 ` to use this class. +There is extensive documentation in the class header. A header with C language bindings is also provided in ``. This is a wrapper around the C++ @@ -158,18 +157,26 @@ for modification and redistribution) unless you have separately 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 build -Dipp_path=/opt/intel/ipp 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 for further build options. 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 for further build options. 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 Notes ---- ------------ ---------- ----- 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. ``` diff --git a/ladspa/RubberBandPitchShifter.cpp b/ladspa/RubberBandPitchShifter.cpp index 868d59f..ae3e45f 100644 --- a/ladspa/RubberBandPitchShifter.cpp +++ b/ladspa/RubberBandPitchShifter.cpp @@ -44,7 +44,7 @@ RubberBandPitchShifter::portNamesMono[PortCountMono] = "Octaves", "Crispness", "Formant Preserving", - "Faster", + "Wet-Dry Mix", "Input", "Output" }; @@ -58,7 +58,7 @@ RubberBandPitchShifter::portNamesStereo[PortCountStereo] = "Octaves", "Crispness", "Formant Preserving", - "Faster", + "Wet-Dry Mix", "Input L", "Output L", "Input R", @@ -112,7 +112,7 @@ RubberBandPitchShifter::hintsMono[PortCountMono] = 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[PortCountMono] = 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[PortCountStereo] = 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[PortCountStereo] = 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::RubberBandPitchShifter(int sampleRate, size_t channels) 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::RubberBandPitchShifter(int sampleRate, size_t channels) m_output = new float *[m_channels]; m_outputBuffer = new RingBuffer *[m_channels]; + m_delayMixBuffer = new RingBuffer *[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(m_bufsize); + m_delayMixBuffer[c] = new RingBuffer(m_bufsize); - m_outputBuffer[c] = new RingBuffer(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::~RubberBandPitchShifter() 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(LADSPA_Handle handle, &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(LADSPA_Handle handle, *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 @@ -431,23 +442,6 @@ RubberBandPitchShifter::updateFormant() m_currentFormant = f; } -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) { @@ -466,15 +460,29 @@ RubberBandPitchShifter::runImpl(unsigned long insamples) 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 long insamples, unsigned long offset) } 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 long insamples, unsigned long offset) 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 long insamples, unsigned long offset) 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 long insamples, unsigned long offset) 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; } } diff --git a/ladspa/RubberBandPitchShifter.h b/ladspa/RubberBandPitchShifter.h index 7f56e11..b954ccd 100644 --- a/ladspa/RubberBandPitchShifter.h +++ b/ladspa/RubberBandPitchShifter.h @@ -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 **m_outputBuffer; + RubberBand::RingBuffer **m_delayMixBuffer; float **m_scratch; int m_sampleRate; diff --git a/main/main.cpp b/main/main.cpp index 4209970..789de9d 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -21,7 +21,7 @@ you must obtain a valid commercial licence before doing so. */ -#include "rubberband/RubberBandStretcher.h" +#include "../rubberband/RubberBandStretcher.h" #include #include @@ -33,20 +33,17 @@ #include -#include "system/sysutils.h" +#include "../src/system/sysutils.h" #ifdef _MSC_VER -#include "getopt/getopt.h" +#include "../src/getopt/getopt.h" #else #include #include #include #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, --pitch Raise pitch by X semitones, or" << endl; cerr << " -f, --frequency Change frequency by multiple X" << endl; cerr << endl; - cerr << " -M, --timemap 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 << "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 << " -M, --timemap Use file F as the source for time map" << endl; cerr << endl; - cerr << "The following options provide a simple way to adjust the sound. See below" << 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 Use file F as the source for pitch map" << endl; + cerr << 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 << " --freqmap 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, --crisp 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, --debug 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 mapping; - - if (mapfile != "") { - std::ifstream ifile(mapfile.c_str()); + std::map 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 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; - ts.setExpectedInputDuration(sfinfo.frames); + 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]; - float *fbuf = new float[channels * ibs]; - float **ibuf = new float *[channels]; - for (size_t i = 0; i < channels; ++i) ibuf[i] = new float[ibs]; + int thisBlockSize; - int frame = 0; - int percent = 0; + while (!successful) { // we may have to repeat with a modified + // gain, if clipping occurs + successful = true; - sf_seek(sndfile, 0, SEEK_SET); + RubberBandStretcher ts(sfinfo.samplerate, channels, options, + ratio, frequencyshift); + ts.setExpectedInputDuration(sfinfo.frames); - if (!realtime) { + int frame = 0; + int percent = 0; - if (!quiet) { - cerr << "Pass 1: Studying..." << endl; + sf_seek(sndfile, 0, SEEK_SET); + + if (!realtime) { + + if (!quiet) { + cerr << "Pass 1: Studying..." << endl; + } + + while (frame < sfinfo.frames) { + + 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); + + ts.study(cbuf, count, final); + + 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::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; - ts.study(ibuf, count, final); + 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; + } + + 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,141 +814,63 @@ int main(int argc, char **argv) } } - frame += ibs; + frame += count; } - if (!quiet) { - cerr << "\rCalculating profile..." << endl; + if (!successful) { + sf_seek(sndfile, 0, SEEK_SET); + sf_seek(sndfileOut, 0, SEEK_SET); + continue; } - - 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; - } + if (!quiet) { + cerr << "\r " << 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]; + int avail; + while ((avail = ts.available()) >= 0) { + if (debug > 1) { + cerr << "(completing) available = " << avail << endl; } - 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]; + + thisBlockSize = avail; + if (thisBlockSize > bs) { + thisBlockSize = bs; } - delete[] obf; - } + + ts.retrieve(cbuf, thisBlockSize); - 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 << "% "; - } - } - - frame += ibs; - } - - if (!quiet) { - cerr << "\r " << endl; - } - int avail; - - while ((avail = ts.available()) >= 0) { - - 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; - - 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(); diff --git a/meson.build b/meson.build index 35700cd..bf2f939 100644 --- a/meson.build +++ b/meson.build @@ -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, diff --git a/meson_options.txt b/meson_options.txt index 86bf686..2f13324 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -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', diff --git a/otherbuilds/Makefile.linux b/otherbuilds/Makefile.linux index b6d4118..ec361bf 100644 --- a/otherbuilds/Makefile.linux +++ b/otherbuilds/Makefile.linux @@ -6,7 +6,7 @@ OPTFLAGS := -DNDEBUG -ffast-math -O3 -ftree-vectorize 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) diff --git a/otherbuilds/check.sh b/otherbuilds/check.sh index 19aee02..0a3c3e5 100755 --- a/otherbuilds/check.sh +++ b/otherbuilds/check.sh @@ -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 - make -f otherbuilds/Makefile.macos - c++ main/main.cpp lib/librubberband.a -I. -Isrc -o test -lsndfile -lsamplerate -framework Accelerate - ./test -V + + 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 + + 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 diff --git a/rubberband/RubberBandStretcher.h b/rubberband/RubberBandStretcher.h index bb0362e..b2ee572 100644 --- a/rubberband/RubberBandStretcher.h +++ b/rubberband/RubberBandStretcher.h @@ -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 diff --git a/rubberband/rubberband-c.h b/rubberband/rubberband-c.h index 887ddf3..1f7451d 100644 --- a/rubberband/rubberband-c.h +++ b/rubberband/rubberband-c.h @@ -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 diff --git a/single/RubberBandSingle.cpp b/single/RubberBandSingle.cpp new file mode 100644 index 0000000..c123206 --- /dev/null +++ b/single/RubberBandSingle.cpp @@ -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" + diff --git a/src/StretchCalculator.cpp b/src/StretchCalculator.cpp index 2886ec3..4f4a304 100644 --- a/src/StretchCalculator.cpp +++ b/src/StretchCalculator.cpp @@ -41,12 +41,14 @@ StretchCalculator::StretchCalculator(size_t sampleRate, 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 &peaks, } } -int -StretchCalculator::calculateSingle(double ratio, - float df, - size_t increment) +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 timeRatio, + double effectivePitchRatio, + float df, + 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(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(double ratio, // 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(double ratio, m_prevDf = df; - bool ratioChanged = (ratio != m_prevRatio); - m_prevRatio = ratio; - - if (isTransient && m_transientAmnesty == 0) { - if (m_debugLevel > 1) { - std::cerr << "StretchCalculator::calculateSingle: transient (df " << df << ", threshold " << transientThreshold << ")" << std::endl; + 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 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 << ", "; + } + + 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 (ratioChanged) { - m_recovery = m_divergence / ((m_sampleRate / 10.0) / increment); + if (m_debugLevel > 1) { + std::cerr << "StretchCalculator::calculateSingle: returning isTransient = " + << isTransient << ", outIncrement = " << outIncrement + << std::endl; } - 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 << ", "; + m_inFrameCounter += inIncrement; + m_outFrameCounter += outIncrement * effectivePitchRatio; + + if (isTransient) { + return -outIncrement; + } else { + return outIncrement; } - if (incr < lrint((increment * ratio) / 2)) { - incr = lrint((increment * ratio) / 2); - } else if (incr > lrint(increment * ratio * 2)) { - incr = lrint(increment * ratio * 2); - } - - double divdiff = (increment * ratio) - incr; - - if (m_debugLevel > 2 || (m_debugLevel > 1 && m_divergence != 0)) { - std::cerr << "divdiff = " << divdiff << std::endl; - } - - 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(0, 0); + m_outFrameCounter = 0.0; m_transientAmnesty = 0; m_keyFrameMap.clear(); } diff --git a/src/StretchCalculator.h b/src/StretchCalculator.h index fc3822d..56c7de7 100644 --- a/src/StretchCalculator.h +++ b/src/StretchCalculator.h @@ -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 m_frameCheckpoint; + int64_t expectedOutFrame(int64_t inFrame, double timeRatio); + double m_outFrameCounter; std::map m_keyFrameMap; std::vector m_peaks; diff --git a/src/StretcherChannelData.h b/src/StretcherChannelData.h index 2147571..aa718e3 100644 --- a/src/StretcherChannelData.h +++ b/src/StretcherChannelData.h @@ -27,8 +27,7 @@ #include "StretcherImpl.h" #include - -//#define EXPERIMENT 1 +#include 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 inputSize; // set only after known (when data ended); -1 previously size_t outCount; - bool draining; - bool outputComplete; + std::atomic draining; + std::atomic outputComplete; FFT *fft; std::map ffts; diff --git a/src/StretcherImpl.cpp b/src/StretcherImpl.cpp index e7c6e46..e3953d5 100644 --- a/src/StretcherImpl.cpp +++ b/src/StretcherImpl.cpp @@ -544,8 +544,8 @@ RubberBandStretcher::Impl::calculateSizes() // 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::calculateSizes() } 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 float *const *input, size_t samples, bo } #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; } diff --git a/src/StretcherImpl.h b/src/StretcherImpl.h index 1f15bd2..3844720 100644 --- a/src/StretcherImpl.h +++ b/src/StretcherImpl.h @@ -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" diff --git a/src/StretcherProcess.cpp b/src/StretcherProcess.cpp index b8f5a49..cf6f545 100644 --- a/src/StretcherProcess.cpp +++ b/src/StretcherProcess.cpp @@ -189,6 +189,8 @@ RubberBandStretcher::Impl::consumeChannel(size_t c, 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(size_t c, bool &any, bool &last) 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::processOneChunk() 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::testInbufReadSpace(size_t c) 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::calculateIncrements(size_t &phaseIncrementRtn, } } + 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(size_t channel, size_t shiftIncrement, boo (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 diff --git a/src/audiocurves/CompoundAudioCurve.cpp b/src/audiocurves/CompoundAudioCurve.cpp index 122de96..45e6926 100644 --- a/src/audiocurves/CompoundAudioCurve.cpp +++ b/src/audiocurves/CompoundAudioCurve.cpp @@ -23,7 +23,7 @@ #include "CompoundAudioCurve.h" -#include "dsp/MovingMedian.h" +#include "../dsp/MovingMedian.h" #include diff --git a/src/audiocurves/CompoundAudioCurve.h b/src/audiocurves/CompoundAudioCurve.h index 123f8a6..0204aa2 100644 --- a/src/audiocurves/CompoundAudioCurve.h +++ b/src/audiocurves/CompoundAudioCurve.h @@ -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 { diff --git a/src/audiocurves/ConstantAudioCurve.h b/src/audiocurves/ConstantAudioCurve.h index a97ac4e..63df9a4 100644 --- a/src/audiocurves/ConstantAudioCurve.h +++ b/src/audiocurves/ConstantAudioCurve.h @@ -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 { diff --git a/src/audiocurves/HighFrequencyAudioCurve.h b/src/audiocurves/HighFrequencyAudioCurve.h index 34db579..80fadd3 100644 --- a/src/audiocurves/HighFrequencyAudioCurve.h +++ b/src/audiocurves/HighFrequencyAudioCurve.h @@ -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 { diff --git a/src/audiocurves/PercussiveAudioCurve.cpp b/src/audiocurves/PercussiveAudioCurve.cpp index 8b63e34..50c3907 100644 --- a/src/audiocurves/PercussiveAudioCurve.cpp +++ b/src/audiocurves/PercussiveAudioCurve.cpp @@ -23,8 +23,8 @@ #include "PercussiveAudioCurve.h" -#include "system/Allocators.h" -#include "system/VectorOps.h" +#include "../system/Allocators.h" +#include "../system/VectorOps.h" #include #include diff --git a/src/audiocurves/PercussiveAudioCurve.h b/src/audiocurves/PercussiveAudioCurve.h index 645d1dd..593726f 100644 --- a/src/audiocurves/PercussiveAudioCurve.h +++ b/src/audiocurves/PercussiveAudioCurve.h @@ -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 { diff --git a/src/audiocurves/SilentAudioCurve.h b/src/audiocurves/SilentAudioCurve.h index 36b7984..501340c 100644 --- a/src/audiocurves/SilentAudioCurve.h +++ b/src/audiocurves/SilentAudioCurve.h @@ -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 { diff --git a/src/audiocurves/SpectralDifferenceAudioCurve.cpp b/src/audiocurves/SpectralDifferenceAudioCurve.cpp index 5579cc0..60f6e46 100644 --- a/src/audiocurves/SpectralDifferenceAudioCurve.cpp +++ b/src/audiocurves/SpectralDifferenceAudioCurve.cpp @@ -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 { diff --git a/src/audiocurves/SpectralDifferenceAudioCurve.h b/src/audiocurves/SpectralDifferenceAudioCurve.h index 66b6133..7d79b79 100644 --- a/src/audiocurves/SpectralDifferenceAudioCurve.h +++ b/src/audiocurves/SpectralDifferenceAudioCurve.h @@ -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 { diff --git a/src/base/Profiler.cpp b/src/base/Profiler.cpp index d96b571..a7ed6c2 100644 --- a/src/base/Profiler.cpp +++ b/src/base/Profiler.cpp @@ -23,7 +23,7 @@ #include "Profiler.h" -#include "system/Thread.h" +#include "../system/Thread.h" #include #include diff --git a/src/base/Profiler.h b/src/base/Profiler.h index bc39107..b9280cb 100644 --- a/src/base/Profiler.h +++ b/src/base/Profiler.h @@ -42,7 +42,7 @@ #ifdef PROFILE_CLOCKS #include #else -#include "system/sysutils.h" +#include "../system/sysutils.h" #ifndef _WIN32 #include #endif diff --git a/src/base/RingBuffer.h b/src/base/RingBuffer.h index 3586b91..152fc6b 100644 --- a/src/base/RingBuffer.h +++ b/src/base/RingBuffer.h @@ -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 +#include + 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 m_writer; + std::atomic m_reader; + const int m_size; + bool m_mlocked; int readSpaceFor(int w, int r) const { int space; @@ -243,7 +245,8 @@ RingBuffer * RingBuffer::resized(int newSize) const { RingBuffer *newBuffer = new RingBuffer(newSize); - + + MBARRIER(); int w = m_writer; int r = m_reader; @@ -273,7 +276,8 @@ RingBuffer::reset() std::cerr << "RingBuffer[" << this << "]::reset" << std::endl; #endif - m_reader = m_writer; + int r = m_reader; + m_writer = r; } template @@ -302,7 +306,6 @@ RingBuffer::read(S *const R__ destination, int n) 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::read(S *const R__ destination, int n) r += n; while (r >= m_size) r -= m_size; - MBARRIER(); m_reader = r; return n; @@ -355,7 +357,6 @@ RingBuffer::readAdding(S *const R__ destination, int n) r += n; while (r >= m_size) r -= m_size; - MBARRIER(); m_reader = r; return n; @@ -377,7 +378,6 @@ RingBuffer::readOne() T value = m_buffer[r]; if (++r == m_size) r = 0; - MBARRIER(); m_reader = r; return value; @@ -447,7 +447,6 @@ RingBuffer::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; diff --git a/src/base/Scavenger.h b/src/base/Scavenger.h index e8b6b5b..b6c045b 100644 --- a/src/base/Scavenger.h +++ b/src/base/Scavenger.h @@ -33,9 +33,9 @@ #include #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 diff --git a/src/dsp/AudioCurveCalculator.h b/src/dsp/AudioCurveCalculator.h index 0b47cd0..9b8dc61 100644 --- a/src/dsp/AudioCurveCalculator.h +++ b/src/dsp/AudioCurveCalculator.h @@ -26,8 +26,7 @@ #include - -#include "system/sysutils.h" +#include "../system/sysutils.h" namespace RubberBand { diff --git a/src/dsp/BQResampler.cpp b/src/dsp/BQResampler.cpp new file mode 100644 index 0000000..c7db9c4 --- /dev/null +++ b/src/dsp/BQResampler.cpp @@ -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 + +#include + +#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 +BQResampler::kaiser(double beta, int len) const +{ + double denominator = bessel0(beta); + int half = (len % 2 == 0 ? len/2 : (len+1)/2); + vector 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 +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 &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 &target_phase_data, + floatbuf &target_phase_sorted_filter, + int filter_length, + const vector *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 +BQResampler::make_filter(int filter_length, double peak_to_zero) const +{ + vector filter; + filter.reserve(filter_length); + + vector 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 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; +} + +} diff --git a/src/dsp/BQResampler.h b/src/dsp/BQResampler.h new file mode 100644 index 0000000..8a441f8 --- /dev/null +++ b/src/dsp/BQResampler.h @@ -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 + +#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 > floatbuf; + + struct state { + params parameters; + int initial_phase; + int current_phase; + int current_channel; + int filter_length; + std::vector 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 m_prototype; + int m_proto_length; + bool m_initialised; + + int gcd(int a, int b) const; + double bessel0(double x) const; + std::vector kaiser(double beta, int len) const; + void kaiser_params(double attenuation, double transition, + double &beta, int &len) const; + std::vector kaiser_for(double attenuation, double transition, + int minlen, int maxlen) const; + void sinc_multiply(double peak_to_zero, std::vector &buf) const; + + params fill_params(double ratio, int num, int denom) const; + params pick_params(double ratio) const; + + std::vector make_filter(int filter_length, + double peak_to_zero) const; + + void phase_data_for(std::vector &target_phase_data, + floatbuf &target_phase_sorted_filter, + int filter_length, + const std::vector *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 diff --git a/src/dsp/FFT.cpp b/src/dsp/FFT.cpp index 5bfc3f7..57c33e6 100644 --- a/src/dsp/FFT.cpp +++ b/src/dsp/FFT.cpp @@ -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 diff --git a/src/dsp/FFT.h b/src/dsp/FFT.h index fbf36a0..42f619d 100644 --- a/src/dsp/FFT.h +++ b/src/dsp/FFT.h @@ -24,7 +24,7 @@ #ifndef RUBBERBAND_FFT_H #define RUBBERBAND_FFT_H -#include "system/sysutils.h" +#include "../system/sysutils.h" #include #include diff --git a/src/dsp/MovingMedian.h b/src/dsp/MovingMedian.h index 50b7eca..bd1b3ed 100644 --- a/src/dsp/MovingMedian.h +++ b/src/dsp/MovingMedian.h @@ -26,7 +26,7 @@ #include "SampleFilter.h" -#include "system/Allocators.h" +#include "../system/Allocators.h" #include diff --git a/src/dsp/Resampler.cpp b/src/dsp/Resampler.cpp index 06199a4..ecfe379 100644 --- a/src/dsp/Resampler.cpp +++ b/src/dsp/Resampler.cpp @@ -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 #include @@ -30,9 +32,6 @@ #include #include -#include "system/Allocators.h" -#include "system/VectorOps.h" - #ifdef HAVE_IPP #include #if (IPP_VERSION_MAJOR < 7) @@ -42,6 +41,10 @@ #endif #endif +#ifdef HAVE_SAMPLERATE +#define HAVE_LIBSAMPLERATE 1 +#endif + #ifdef HAVE_LIBSAMPLERATE #include #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 /* quality */, 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 /* quality */, 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 /* quality */, 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 R__ out, } 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 R__ out, } 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 R__ out, } 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 R__ out, } 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 R__ out, } 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 ratio, bool final) 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 ratio, bool final) 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 ratio, bool final) 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 ratio, bool final) 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 ratio, bool final) 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 ratio, bool final) } 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 ratio, bool final) 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 ratio, bool final) } 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, int channels, double, 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 R__ out, } 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 R__ out, 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 R__ out, 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 R__ out, 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::Quality quality, 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__ *const R__ out, } 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(m_iinsize); + m_iout = allocate(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(m_iin, m_iinsize, incount * m_channels); + m_iinsize = incount * m_channels; + } + if (outcount * m_channels > m_ioutsize) { + m_iout = reallocate(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 quality, 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__ *const R__ out, } 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::Parameters params, int channels) #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::Parameters params, int channels) #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::Parameters params, int channels) #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::Parameters params, int channels) 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::Parameters params, int channels) 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::Parameters params, int channels) 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,12 +1497,21 @@ Resampler::Resampler(Resampler::Parameters params, int channels) case 3: #ifdef HAVE_LIBRESAMPLE d = new Resamplers::D_Resample - (params.quality, + (params.quality, params.ratioChange, channels, params.initialSampleRate, params.maxBufferSize, params.debugLevel); #else cerr << "Resampler::Resampler: No implementation available!" << endl; 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; } @@ -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() { diff --git a/src/dsp/Resampler.h b/src/dsp/Resampler.h index 4a1f723..88a4b85 100644 --- a/src/dsp/Resampler.h +++ b/src/dsp/Resampler.h @@ -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; diff --git a/src/dsp/SincWindow.h b/src/dsp/SincWindow.h index 43670ad..b9fe971 100644 --- a/src/dsp/SincWindow.h +++ b/src/dsp/SincWindow.h @@ -29,9 +29,9 @@ #include #include -#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 { diff --git a/src/dsp/Window.h b/src/dsp/Window.h index 52893a9..52f356c 100644 --- a/src/dsp/Window.h +++ b/src/dsp/Window.h @@ -28,9 +28,9 @@ #include #include -#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 { diff --git a/src/rubberband-c.cpp b/src/rubberband-c.cpp index 03dfa6b..4230581 100644 --- a/src/rubberband-c.cpp +++ b/src/rubberband-c.cpp @@ -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_ { diff --git a/src/system/Allocators.h b/src/system/Allocators.h index 355bf29..6f1df54 100644 --- a/src/system/Allocators.h +++ b/src/system/Allocators.h @@ -29,6 +29,8 @@ #include // for std::bad_alloc #include +#include + #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 > v; + */ +template +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 StlAllocator(const StlAllocator&) { } + ~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(n); + } + + void + deallocate(T *const p, const size_t) const { + RubberBand::deallocate(p); + } + + template + 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(0) - static_cast(1)) / sizeof(T); + } + + template struct rebind { + typedef StlAllocator 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(p); + new (pv) T(t); + } + + void + destroy(T *const p) const { + p->~T(); + } + +private: + StlAllocator& operator=(const StlAllocator&); +}; + } #endif diff --git a/src/system/VectorOps.h b/src/system/VectorOps.h index ced40fc..5287be7 100644 --- a/src/system/VectorOps.h +++ b/src/system/VectorOps.h @@ -491,6 +491,58 @@ inline T v_sum(const T *const R__ src, return result; } +template +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 inline void v_log(T *const R__ dst, const int count) diff --git a/src/system/sysutils.h b/src/system/sysutils.h index 86a2896..40f24cc 100644 --- a/src/system/sysutils.h +++ b/src/system/sysutils.h @@ -135,8 +135,8 @@ extern void system_memorybarrier(); #include #include -#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__