diff --git a/.appveyor.yml b/.appveyor.yml index 825ae67..1c826c1 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -16,7 +16,11 @@ build_script: - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat" - meson build "-Dextra_include_dirs=C:\Program Files\libsndfile\include" "-Dextra_lib_dirs=C:\Program Files\libsndfile\lib" - ninja -C build + - meson test -C build # Test the VC++ static library build, which is separate - msbuild otherbuilds\rubberband-library.vcxproj /t:Build /p:Configuration=Release - # And test the .NET FFI interface build, which is again separate + # Test the .NET FFI interface build, which is again separate - msbuild dotnet\rubberband.sln /t:Restore;Build + # And test the single-file build + - cl main\main.cpp single\RubberBandSingle.cpp .\src\ext\getopt\getopt.c src\ext\getopt\getopt_long.c "C:\Program Files\libsndfile\lib\sndfile.lib" /O2 /std:c++14 /D_USE_MATH_DEFINES /DNOMINMAX /EHs /I"C:\Program Files\libsndfile\include" /link /out:test_single.exe + diff --git a/.build.yml b/.build.yml index 5402296..52785d3 100644 --- a/.build.yml +++ b/.build.yml @@ -7,6 +7,7 @@ packages: - ladspa-sdk - lv2-dev - vamp-plugin-sdk + - libboost-test-dev - meson - ninja-build sources: @@ -15,9 +16,27 @@ tasks: - setup: | cd rubberband meson build + meson build_speex -Dresampler=speex + meson build_libsamplerate -Dresampler=libsamplerate + meson build_fftw -Dfft=fftw + meson build_kissfft -Dfft=kissfft - build: | cd rubberband ninja -C build + meson test -C build + build/rubberband -V + ninja -C build_speex + meson test -C build_speex + build_speex/rubberband -V + ninja -C build_libsamplerate + meson test -C build_libsamplerate + build_libsamplerate/rubberband -V + ninja -C build_fftw + meson test -C build_fftw + build_fftw/rubberband -V + ninja -C build_kissfft + meson test -C build_kissfft + build_kissfft/rubberband -V ./otherbuilds/check.sh triggers: - action: email diff --git a/.github/workflows/macos-ios.yml b/.github/workflows/macos-ios.yml index 2c1e5f9..fdc7d3b 100644 --- a/.github/workflows/macos-ios.yml +++ b/.github/workflows/macos-ios.yml @@ -21,6 +21,8 @@ jobs: run: ninja -C build_macos - name: make ios run: ninja -C build_ios + - name: unit test macos + run: meson test -C build_macos - name: check otherbuilds run: otherbuilds/check.sh diff --git a/.hgignore b/.hgignore index a335526..68d2bc3 100644 --- a/.hgignore +++ b/.hgignore @@ -26,3 +26,7 @@ build build_* build-* UpgradeLog* +out-*/ +playlist-out/* +formant-out-*/ +out*.wav diff --git a/.hgtags b/.hgtags index 8711a89..8fe0780 100644 --- a/.hgtags +++ b/.hgtags @@ -16,3 +16,10 @@ fa6a54be7e6bf0c5adffd19ccec622481a8140a5 v1.8.2 4a6f7059b6b77fb34a9f29037f3ece47755e99a0 v2.0.0 190ba65557c06823ef576d5d62b99e2d27771759 v2.0.1 4e2177c66756fecacccf211df5f8a97d01070ef0 v2.0.2 +590cb5c496f868d9806db5205bfe22ddeb7f5767 v3.0.0-beta1 +acc04c20175ebc3f8138c4e14c9108cb33024fb1 v3.0.0-beta2 +acc04c20175ebc3f8138c4e14c9108cb33024fb1 v3.0.0-beta2 +ed9acf241b1076e84ba0b41dc9d8edd904f69f25 v3.0.0-beta2 +58b588a580a126adb16de96f8eade96111758085 v3.0.0-beta3 +58b588a580a126adb16de96f8eade96111758085 v3.0.0-beta3 +78a701fb6daa670fc49648816f040a70689c86a7 v3.0.0-beta3 diff --git a/CHANGELOG b/CHANGELOG index ecb4617..4a67b9c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,27 @@ +Changes in Rubber Band v3.0.0 + + * Introduce a new processing engine, the R3 (Finer) engine, which + typically produces higher-quality output than the existing R2 + engine but at significantly higher CPU cost. The R2 engine is + still the default, and R3 can be selected using the new + OptionEngineFiner option on construction. See the documentation + for more details. + * Add ability to provide a custom set of log callbacks, so that + debug and warning logs can be routed to the application's log + stream and/or handled in a realtime-safe way + * Add option to shift formant independently of pitch (R3 engine + only) + +The library is both binary and API compatible all the way back to +version 1.2 for forward compatibility, but not backward compatibility, +as several new functions and enum values have been added. Code +written to use 3.0 is not necessarily compatible with 2.x or 1.x, but +code written to use any earlier version can update to 3.0 without +modification (and it will continue to use the same processing engine +if the calling code is unchanged). + + Changes in Rubber Band v2.0.2 * Fix a crash in certain configurations when using mid-side diff --git a/COMPILING.md b/COMPILING.md new file mode 100644 index 0000000..16b0f3b --- /dev/null +++ b/COMPILING.md @@ -0,0 +1,524 @@ + +# Compiling Rubber Band Library + +## Contents of this file + +1. General instructions +2. Building on Linux +3. Building on macOS +4. Building for iOS +5. Building on Windows +6. Building for Android and Java integration +7. FFT and resampler selection +8. Other supported #defines +9. Copyright notes for bundled libraries + + +## 1. General instructions + +**Full configurable build.** 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. See below for details. + +**Single-file build.** If you want to include Rubber Band in a C++ +project and would prefer not to build it as 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 using the built-in FFT and resampler implementations with no +further library dependencies. See the comments at the top of that file +for more information. + +**Other build options.** 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. + +To build with Meson, ensure Meson and Ninja are installed and run: + +``` +$ meson build && ninja -C build +``` + +This checks for necessary dependencies, reports what it finds, and if +all is well, builds the code into a subdirectory called `build`. It +will build everything it can find the requisite dependencies for: +static and dynamic libraries, LADSPA, LV2, and Vamp plugins, and +command-line utility. + +Some configuration options are provided, described in the +`meson_options.txt` file. To set one of these, add a `-D` option to +Meson: + +``` +$ meson build -Dipp_path=/opt/intel/ipp +``` + +The options are documented in the library- and platform-specific +sections below. + +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 +libraries in `src/speex/COPYING` and `src/kissfft/COPYING` as +applicable. + + +## 2. Building on Linux + +Optionally, if you want the command-line tool and plugins to be built, +first install libsndfile and the LADSPA, LV2, and Vamp plugin headers +so they can be found using `pkg-config`. Then + +``` +$ meson build && ninja -C build +``` + +See "FFT and resampler selection" below for further build options. + +Alternatively, if you only need the static library and prefer a +Makefile, try + +``` +$ make -f otherbuilds/Makefile.linux +``` + + +## 3. Building on macOS + +Ensure the Xcode command-line tools are installed, and if you want the +command-line tool to be built, also install libsndfile. + +To build for the default architecture: + +``` +$ meson build && ninja -C build +``` + +Which architecture is the default may depend on the version of Meson +and/or the current shell. To force a particular architecture you can +use a Meson cross-file, as follows. + +To build for Apple Silicon (arm64): + +``` +$ meson build --cross-file cross/macos-arm64.txt && ninja -C build +``` + +To build for Intel (x86_64): + +``` +$ meson build --cross-file cross/macos-x86_64.txt && ninja -C build +``` + +You can build a universal binary library for both architectures like +this: + +``` +$ meson build --cross-file cross/macos-universal.txt && ninja -C build +``` + +Note that the universal cross file also sets the minimum OS version to +the earliest supported macOS versions for both architectures. (In +practice, compatibility will also depend on how the dependent +libraries have been compiled.) You can edit this in the +`cross/macos-universal.txt` file if you want a specific target. + +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 Rubber Band Library. GPL code is not permitted in the app +store. See https://breakfastquay.com/technology/license.html for +commercial terms. + + +## 4. Building for iOS + +Ensure the Xcode command-line tools are installed, and + +``` +$ meson build_ios --cross-file cross/ios.txt && ninja -C build_ios +``` + +The output files will be found in the `build_ios` directory. + +To build for the simulator, + +``` +$ meson build_sim --cross-file cross/ios-simulator.txt && ninja -C build_sim +``` + +The output files will be found in the `build_sim` directory. + +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 Rubber Band Library. GPL code is not permitted in the app +store. See https://breakfastquay.com/technology/license.html for +commercial terms. + + +## 5. Building on Windows + +If you only need to build the static library for integration into your +project, and you prefer a Visual Studio project file, you can find a +simple one in `otherbuilds\rubberband-library.vcxproj`. + +The rest of this section describes the "full" build system, which uses +Meson just as on the other platforms. So to build this way, start by +ensuring Meson and Ninja are installed and available. Then, in a +terminal window with the compiler tools available in the path (e.g. a +Visual Studio command-line prompt for the relevant build architecture) +run + +``` +> meson build +> ninja -C build +``` + +The output files will be found in the `build` directory. + +The Rubber Band code is compatible with both the traditional Visual +C++ compiler (`cl`) and the Clang front-end (`clang`), and the build +system will use whichever appears (first) in your path. + +To build against a specific Visual C++ runtime, use the built-in Meson +option `b_vscrt`: + +``` +> meson build -Db_vscrt=mt +``` + +See "FFT and resampler selection" below for further build options. + + +## 6. Building for Android and Java integration + +Currently only a very old Android NDK build file is provided, as +`otherbuilds/Android.mk`. This includes compile definitions for a +shared library built for ARM architectures which can be loaded from a +Java application using the Java native interface (i.e. the Android +NDK). + +The Java side of the interface can be found in +`com/breakfastquay/rubberband/RubberBandStretcher.java`. + +See +https://hg.sr.ht/~breakfastquay/rubberband-android-simple-sample +for a very trivial example of integration with Android Java code. + +The supplied `.mk` file uses KissFFT and the Speex resampler. + + +## 7. FFT and resampler selection + +Rubber Band requires the selection of library code for FFT calculation +and resampling. Several libraries are supported. The selection is +controlled (in Meson) using `-D` options and (in the code itself) +using preprocessor flags set by the build system. These options and +flags are detailed in the tables below. + +At least one resampler implementation and one FFT implementation must +be enabled. It is technically possible to enable more than one, but +it's confusing and not often useful. + +If you are building this software using the bundled Speex or KissFFT +library code, please be sure to review the terms for those libraries +in `src/speex/COPYING` and `src/kissfft/COPYING` as applicable. + +If you are proposing to package Rubber Band for a Linux distribution, +please select either the built-in FFT or FFTW, and either the built-in +resampler or libsamplerate. + +### FFT libraries supported + +``` +Library Build option CPP define Notes +---- ------------ ---------- ----- + +Built-in -Dfft=builtin -DUSE_BUILTIN_FFT + Default except on macOS/iOS. + Can be distributed with either + the Rubber Band GPL or + commercial licence. + +Accelerate -Dfft=vdsp -DHAVE_VDSP Default on macOS/iOS. + Best option on these platforms. + +FFTW3 -Dfft=fftw -DHAVE_FFTW3 GPL. + A bit faster than built-in, + a bit slower than Accelerate. + +KissFFT -Dfft=kissfft -DHAVE_KISSFFT + Single precision. + Only indicated for use with + single-precision sample type + (see below). + Bundled, can be distributed with + either the Rubber Band GPL or + commercial licence. + +Intel IPP -Dfft=ipp -DHAVE_IPP Proprietary, can only be used with + Rubber Band commercial licence. +``` + +### Resampler libraries supported + +``` +Library Build option CPP define Notes +---- ------------ ---------- ----- + +Built-in -Dfft=builtin -DUSE_BQRESAMPLER + Default. + 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. + +libsamplerate -DHAVE_LIBSAMPLERATE + -Dresampler=libsamplerate Good choice in most cases. + +Speex -DUSE_SPEEX + -Dresampler=speex Can be distributed with + either the Rubber Band GPL or + commercial licence. +``` + +## 8. Other supported #defines + +Other known preprocessor symbols are as follows. (Usually the supplied +build files will handle these for you.) + + -DLACK_BAD_ALLOC + Define on systems lacking std::bad_alloc in the C++ library. + + -DLACK_POSIX_MEMALIGN + Define on systems lacking posix_memalign. + + -DUSE_OWN_ALIGNED_MALLOC + Define on systems lacking any aligned malloc implementation. + + -DLACK_SINCOS + Define on systems lacking sincos(). + + -DNO_EXCEPTIONS + Build without use of C++ exceptions. + + -DNO_THREADING + Build without any multithread support. + + -DUSE_PTHREADS + Use the pthreads library (required unless NO_THREADING or on Windows) + + -DPROCESS_SAMPLE_TYPE=float + Select single precision for internal calculations. The default is + double precision. Consider in conjunction with single-precision + KissFFT for mobile architectures with slower double-precision + support. + + -DUSE_POMMIER_MATHFUN + Select the Julien Pommier implementations of trig functions for ARM + NEON or x86 SSE architectures. These are usually faster but may be + of lower precision than system implementations. Consider using this + for 32-bit mobile architectures. + + +## 9. Copyright notes for bundled libraries + +### 5a. Speex + +``` +[files in src/speex] + +Copyright 2002-2007 Xiph.org Foundation +Copyright 2002-2007 Jean-Marc Valin +Copyright 2005-2007 Analog Devices Inc. +Copyright 2005-2007 Commonwealth Scientific and Industrial Research + Organisation (CSIRO) +Copyright 1993, 2002, 2006 David Rowe +Copyright 2003 EpicGames +Copyright 1992-1994 Jutta Degener, Carsten Bormann + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +- Neither the name of the Xiph.org Foundation nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +### 5b. KissFFT + +``` +[files in src/kissfft] + +Copyright (c) 2003-2004 Mark Borgerding + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the author nor the names of any contributors may be used + to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +### 5c. Pommier math functions + +``` +[files in src/pommier] + +Copyright (C) 2011 Julien Pommier + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +``` + +### 5d. float_cast + +``` +[files in src/float_cast] + +Copyright (C) 2001 Erik de Castro Lopo + +Permission to use, copy, modify, distribute, and sell this file for any +purpose is hereby granted without fee, provided that the above copyright +and this permission notice appear in all copies. No representations are +made about the suitability of this software for any purpose. It is +provided "as is" without express or implied warranty. +``` + +### 5e. getopt + +``` +[files in src/getopt, used by command-line tool on some platforms] + +Copyright (c) 2000 The NetBSD Foundation, Inc. +All rights reserved. + +This code is derived from software contributed to The NetBSD Foundation +by Dieter Baron and Thomas Klausner. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. All advertising materials mentioning features or use of this software + must display the following acknowledgement: + This product includes software developed by the NetBSD + Foundation, Inc. and its contributors. +4. Neither the name of The NetBSD Foundation nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +``` + +### 5f. rubberband-sharp + +``` +[files in rubberband-dll and rubberband-sharp] + +Copyright 2018-2019 Jonathan Gilbert + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Jonathan Gilbert +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in this Software without prior written +authorization. +``` diff --git a/Doxyfile b/Doxyfile index bc70bb7..54f01b3 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 = 2.0.2 +PROJECT_NUMBER = 3.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 ffe204c..fd57943 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,8 @@ responsibility to ensure that you redistribute these only in accordance with their own licence terms, regardless of the conditions under which you are redistributing the Rubber Band code itself. The licences for some relevant library code are as follows, to the best of -our knowledge. See also the end of this README for detailed terms. +our knowledge. See also the file [COMPILING.md](COMPILING.md) for more +details. * FFTW3 - GPL; proprietary licence needed for redistribution * Intel IPP - Proprietary; licence needed for redistribution @@ -63,20 +64,17 @@ our knowledge. See also the end of this README for detailed terms. * Pommier math functions - BSD-like +## Compiling Rubber Band Library + +Please refer to the file [COMPILING.md](COMPILING.md) for details of +how to configure and build the library. + + ## Contents of this README 1. Code components 2. Using the Rubber Band command-line tool 3. Using Rubber Band Library -4. Compiling Rubber Band: - a. Building on Linux - b. Building on macOS - c. Building for iOS - d. Building on Windows - e. Building for Android and Java integration - f. FFT and resampler selection - g. Other supported #defines -5. Copyright notes for bundled libraries ## 1. Code components @@ -87,7 +85,8 @@ Rubber Band consists of: be used by your applications. The headers for this are in the `rubberband/` directory, and the source code is in `src/`. The Rubber Band Library may also depend upon external resampler - and FFT code; see section 4 below for details. + and FFT code, if so configured; see section 7 of COMPILING.md for + details. * The Rubber Band command-line tool. This is in `main/main.cpp`. This program uses Rubber Band Library and also requires libsndfile @@ -106,7 +105,7 @@ Rubber Band consists of: ## 2. Using the Rubber Band command-line tool -The Rubber Band command-line tool builds as `bin/rubberband`. The +The Rubber Band command-line tool builds as `build/rubberband`. The basic incantation is ``` @@ -124,8 +123,25 @@ duration, shifts it up in pitch by a whole tone, and writes the output to `output.wav`. Several further options are available: run `rubberband -h` for help. -In particular, different types of music may benefit from different -"crispness" options (`-c` flag, with a numerical argument from 0 to 6). + +The most important are the options `-2` and `-3`. These select between +two different processing engines, known as the R2 (Faster) engine and +the R3 (Finer) engine. The R3 engine produces higher-quality results +than R2 for most material, especially complex mixes, vocals and other +sounds that have soft onsets and smooth pitch changes, and music with +substantial bass content. However, it uses much more CPU than the R2 +engine. + +The R2 engine was the only method available in Rubber Band Library up +to versions 2.x, and for compatibility it remains the default (in the +case that neither `-2` nor `-3` is requested explicitly) whenever the +command-line tool is invoked as `rubberband`. The R3 engine is the +default if the tool is invoked as `rubberband-r3`. + +Many further options are available, most of which only have an effect +when using the R2 engine. In particular, different types of music may +benefit from different "crispness" options (`-c` flag, with a +numerical argument from 0 to 6). ## 3. Using Rubber Band Library @@ -150,520 +166,19 @@ provides a good example of how to use Rubber Band in offline mode; the pitch shifter plugin (`ladspa-lv2/RubberBandPitchShifter.cpp`) may be used as an example of Rubber Band in real-time mode. -IMPORTANT: Please ensure you have read and understood the licensing -terms for Rubber Band before using it in your application. This -library is provided under the GNU General Public License, which means -that any application that uses it must also be published under the GPL -or a compatible licence (i.e. with its full source code also available -for modification and redistribution) unless you have separately -acquired a commercial licence from the author. - - -## 4. Compiling Rubber Band Library - -**Full configurable build.** 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. See below for details. - -**Single-file build.** If you want to include Rubber Band in a C++ -project and would prefer not to build it as 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 using the built-in FFT and resampler implementations with no -further library dependencies. See the comments at the top of that file -for more information. - -**Other build options.** 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. - -To build with Meson, ensure Meson and Ninja are installed and run: - -``` -$ meson build && ninja -C build -``` - -This checks for necessary dependencies, reports what it finds, and if -all is well, builds the code into a subdirectory called `build`. It -will build everything it can find the requisite dependencies for: -static and dynamic libraries, LADSPA, LV2, and Vamp plugins, and -command-line utility. - -Some configuration options are provided, described in the -`meson_options.txt` file. To set one of these, add a `-D` option to -Meson: - -``` -$ meson build -Dipp_path=/opt/intel/ipp -``` - -The options are documented in the library- and platform-specific -sections below. - -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 -libraries in `src/speex/COPYING` and `src/kissfft/COPYING` as -applicable. - - -### 4a. Building on Linux - -Optionally, if you want the command-line tool and plugins to be built, -first install libsndfile and the LADSPA, LV2, and Vamp plugin headers -so they can be found using `pkg-config`. Then - -``` -$ meson build && ninja -C build -``` - -See "FFT and resampler selection" below for further build options. - -Alternatively, if you only need the static library and prefer a -Makefile, try - -``` -$ make -f otherbuilds/Makefile.linux -``` - - -### 4b. Building on macOS - -Ensure the Xcode command-line tools are installed, and if you want the -command-line tool to be built, also install libsndfile. - -To build for the default architecture: - -``` -$ meson build && ninja -C build -``` - -Which architecture is the default may depend on the version of Meson -and/or the current shell. To force a particular architecture you can -use a Meson cross-file, as follows. - -To build for Apple Silicon (arm64): - -``` -$ meson build --cross-file cross/macos-arm64.txt && ninja -C build -``` - -To build for Intel (x86_64): - -``` -$ meson build --cross-file cross/macos-x86_64.txt && ninja -C build -``` - -You can build a universal binary library for both architectures like -this: - -``` -$ meson build --cross-file cross/macos-universal.txt && ninja -C build -``` - -Note that the universal cross file also sets the minimum OS version to -the earliest supported macOS versions for both architectures. (In -practice, compatibility will also depend on how the dependent -libraries have been compiled.) You can edit this in the -`cross/macos-universal.txt` file if you want a specific target. - -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 Rubber Band Library. GPL code is not permitted in the app -store. See https://breakfastquay.com/technology/license.html for -commercial terms. - - -### 4c. Building for iOS - -Ensure the Xcode command-line tools are installed, and - -``` -$ meson build_ios --cross-file cross/ios.txt && ninja -C build_ios -``` - -The output files will be found in the `build_ios` directory. - -To build for the simulator, - -``` -$ meson build_sim --cross-file cross/ios-simulator.txt && ninja -C build_sim -``` - -The output files will be found in the `build_sim` directory. - -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 Rubber Band Library. GPL code is not permitted in the app -store. See https://breakfastquay.com/technology/license.html for -commercial terms. - - -### 4d. Building on Windows - -If you only need to build the static library for integration into your -project, and you prefer a Visual Studio project file, you can find a -simple one in `otherbuilds\rubberband-library.vcxproj`. - -The rest of this section describes the "full" build system, which uses -Meson just as on the other platforms. So to build this way, start by -ensuring Meson and Ninja are installed and available. Then, in a -terminal window with the compiler tools available in the path (e.g. a -Visual Studio command-line prompt for the relevant build architecture) -run - -``` -> meson build -> ninja -C build -``` - -The output files will be found in the `build` directory. - -The Rubber Band code is compatible with both the traditional Visual -C++ compiler (`cl`) and the Clang front-end (`clang`), and the build -system will use whichever appears (first) in your path. - -To build against a specific Visual C++ runtime, use the built-in Meson -option `b_vscrt`: - -``` -> meson build -Db_vscrt=mt -``` - -See "FFT and resampler selection" below for further build options. - - -### 4e. Building for Android and Java integration - -Currently only a very old Android NDK build file is provided, as -`otherbuilds/Android.mk`. This includes compile definitions for a -shared library built for ARM architectures which can be loaded from a -Java application using the Java native interface (i.e. the Android -NDK). - -The Java side of the interface can be found in -`com/breakfastquay/rubberband/RubberBandStretcher.java`. - -See -https://hg.sr.ht/~breakfastquay/rubberband-android-simple-sample -for a very trivial example of integration with Android Java code. - -The supplied `.mk` file uses KissFFT and the Speex resampler. - - -### 4f. FFT and resampler selection - -Rubber Band requires the selection of library code for FFT calculation -and resampling. Several libraries are supported. The selection is -controlled (in Meson) using `-D` options and (in the code itself) -using preprocessor flags set by the build system. These options and -flags are detailed in the tables below. - -At least one resampler implementation and one FFT implementation must -be enabled. It is technically possible to enable more than one, but -it's confusing and not often useful. - -If you are building this software using the bundled Speex or KissFFT -library code, please be sure to review the terms for those libraries -in `src/speex/COPYING` and `src/kissfft/COPYING` as applicable. - -If you are proposing to package Rubber Band for a Linux distribution, -please select either the built-in FFT or FFTW, and either the built-in -resampler or libsamplerate. - -#### FFT libraries supported - -``` -Library Build option CPP define Notes ----- ------------ ---------- ----- - -Built-in -Dfft=builtin -DUSE_BUILTIN_FFT - Default except on macOS/iOS. - Can be distributed with either - the Rubber Band GPL or - commercial licence. - -Accelerate -Dfft=vdsp -DHAVE_VDSP Default on macOS/iOS. - Best option on these platforms. - -FFTW3 -Dfft=fftw -DHAVE_FFTW3 GPL. - A bit faster than built-in, - a bit slower than Accelerate. - -KissFFT -Dfft=kissfft -DHAVE_KISSFFT - Single precision. - Only indicated for use with - single-precision sample type - (see below). - Bundled, can be distributed with - either the Rubber Band GPL or - commercial licence. - -Intel IPP -Dfft=ipp -DHAVE_IPP Proprietary, can only be used with - Rubber Band commercial licence. -``` - -#### Resampler libraries supported - -``` -Library Build option CPP define Notes ----- ------------ ---------- ----- - -Built-in -Dfft=builtin -DUSE_BQRESAMPLER - Default. - 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. - -libsamplerate -DHAVE_LIBSAMPLERATE - -Dresampler=libsamplerate Good choice in most cases. - -Speex -DUSE_SPEEX - -Dresampler=speex Can be distributed with - either the Rubber Band GPL or - commercial licence. -``` - -### 4g. Other supported #defines - -Other known preprocessor symbols are as follows. (Usually the supplied -build files will handle these for you.) - - -DLACK_BAD_ALLOC - Define on systems lacking std::bad_alloc in the C++ library. - - -DLACK_POSIX_MEMALIGN - Define on systems lacking posix_memalign. - - -DUSE_OWN_ALIGNED_MALLOC - Define on systems lacking any aligned malloc implementation. - - -DLACK_SINCOS - Define on systems lacking sincos(). - - -DNO_EXCEPTIONS - Build without use of C++ exceptions. - - -DNO_THREADING - Build without any multithread support. - - -DUSE_PTHREADS - Use the pthreads library (required unless NO_THREADING or on Windows) - - -DPROCESS_SAMPLE_TYPE=float - Select single precision for internal calculations. The default is - double precision. Consider in conjunction with single-precision - KissFFT for mobile architectures with slower double-precision - support. - - -DUSE_POMMIER_MATHFUN - Select the Julien Pommier implementations of trig functions for ARM - NEON or x86 SSE architectures. These are usually faster but may be - of lower precision than system implementations. Consider using this - for 32-bit mobile architectures. - - -## 5. Copyright notes for bundled libraries - -### 5a. Speex - -``` -[files in src/speex] - -Copyright 2002-2007 Xiph.org Foundation -Copyright 2002-2007 Jean-Marc Valin -Copyright 2005-2007 Analog Devices Inc. -Copyright 2005-2007 Commonwealth Scientific and Industrial Research - Organisation (CSIRO) -Copyright 1993, 2002, 2006 David Rowe -Copyright 2003 EpicGames -Copyright 1992-1994 Jutta Degener, Carsten Bormann - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -- Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. - -- Neither the name of the Xiph.org Foundation nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -``` - -### 5b. KissFFT - -``` -[files in src/kissfft] - -Copyright (c) 2003-2004 Mark Borgerding - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the author nor the names of any contributors may be used - to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -``` - -### 5c. Pommier math functions - -``` -[files in src/pommier] - -Copyright (C) 2011 Julien Pommier - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. -``` - -### 5d. float_cast - -``` -[files in src/float_cast] - -Copyright (C) 2001 Erik de Castro Lopo - -Permission to use, copy, modify, distribute, and sell this file for any -purpose is hereby granted without fee, provided that the above copyright -and this permission notice appear in all copies. No representations are -made about the suitability of this software for any purpose. It is -provided "as is" without express or implied warranty. -``` - -### 5e. getopt - -``` -[files in src/getopt, used by command-line tool on some platforms] - -Copyright (c) 2000 The NetBSD Foundation, Inc. -All rights reserved. - -This code is derived from software contributed to The NetBSD Foundation -by Dieter Baron and Thomas Klausner. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. All advertising materials mentioning features or use of this software - must display the following acknowledgement: - This product includes software developed by the NetBSD - Foundation, Inc. and its contributors. -4. Neither the name of The NetBSD Foundation nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS -``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED -TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. -``` - -### 5f. rubberband-sharp - -``` -[files in rubberband-dll and rubberband-sharp] - -Copyright 2018-2019 Jonathan Gilbert - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, copy, -modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR -ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF -CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Except as contained in this notice, the name of Jonathan Gilbert -shall not be used in advertising or otherwise to promote the sale, -use or other dealings in this Software without prior written -authorization. -``` +**IMPORTANT:** Please ensure you have read and understood the +licensing terms for Rubber Band before using it in your application. +This library is provided under the GNU General Public License, which +means that any application that uses it must also be published under +the GPL or a compatible licence (i.e. with its full source code also +available for modification and redistribution) unless you have +separately acquired a commercial licence from the author. + + +## 4. Further documentation + + * The [API documentation](https://breakfastquay.com/rubberband/code-doc/index.html) is thorough and we encourage you to read it + * [Conceptual notes and examples](https://breakfastquay.com/rubberband/integration.html) for integration into an application + * [Help text](https://breakfastquay.com/rubberband/usage.txt) of the command-line application + * [Rubber Band Library home page](https://breakfastquay.com/rubberband/) + diff --git a/dotnet/rubberband-library.vcxproj b/dotnet/rubberband-library.vcxproj index 8fbddaf..3f84bb1 100644 --- a/dotnet/rubberband-library.vcxproj +++ b/dotnet/rubberband-library.vcxproj @@ -77,7 +77,7 @@ Disabled ..;..\src;%(AdditionalIncludeDirectories) - __MSVC__;WIN32;_DEBUG;_LIB;NOMINMAX;_USE_MATH_DEFINES;USE_BUILTIN_FFT;USE_SPEEX;%(PreprocessorDefinitions) + __MSVC__;WIN32;_DEBUG;_LIB;NOMINMAX;_USE_MATH_DEFINES;USE_BUILTIN_FFT;USE_BQRESAMPLER;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL @@ -91,7 +91,7 @@ Disabled ..;..\src;%(AdditionalIncludeDirectories) - __MSVC__;WIN32;_DEBUG;_LIB;NOMINMAX;_USE_MATH_DEFINES;USE_BUILTIN_FFT;USE_SPEEX;%(PreprocessorDefinitions) + __MSVC__;WIN32;_DEBUG;_LIB;NOMINMAX;_USE_MATH_DEFINES;USE_BUILTIN_FFT;USE_BQRESAMPLER;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL @@ -109,7 +109,7 @@ Speed true ..;..\src;%(AdditionalIncludeDirectories) - __MSVC__;WIN32;NDEBUG;_LIB;NOMINMAX;_USE_MATH_DEFINES;USE_BUILTIN_FFT;NO_TIMING;USE_SPEEX;NO_THREAD_CHECKS;%(PreprocessorDefinitions) + __MSVC__;WIN32;NDEBUG;_LIB;NOMINMAX;_USE_MATH_DEFINES;USE_BUILTIN_FFT;NO_TIMING;USE_BQRESAMPLER;NO_THREAD_CHECKS;%(PreprocessorDefinitions) MultiThreadedDLL false StreamingSIMDExtensions @@ -127,7 +127,7 @@ Speed true ..;..\src;%(AdditionalIncludeDirectories) - __MSVC__;WIN32;NDEBUG;_LIB;NOMINMAX;_USE_MATH_DEFINES;USE_BUILTIN_FFT;NO_TIMING;USE_SPEEX;NO_THREAD_CHECKS;%(PreprocessorDefinitions) + __MSVC__;WIN32;NDEBUG;_LIB;NOMINMAX;_USE_MATH_DEFINES;USE_BUILTIN_FFT;NO_TIMING;USE_BQRESAMPLER;NO_THREAD_CHECKS;%(PreprocessorDefinitions) MultiThreadedDLL false StreamingSIMDExtensions @@ -139,55 +139,26 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/ladspa-lv2/RubberBandPitchShifter.h b/ladspa-lv2/RubberBandPitchShifter.h index 15678e1..7b3a83d 100644 --- a/ladspa-lv2/RubberBandPitchShifter.h +++ b/ladspa-lv2/RubberBandPitchShifter.h @@ -40,7 +40,7 @@ #include #endif -#include "base/RingBuffer.h" +#include "common/RingBuffer.h" namespace RubberBand { class RubberBandStretcher; diff --git a/ladspa-lv2/RubberBandR3PitchShifter.cpp b/ladspa-lv2/RubberBandR3PitchShifter.cpp new file mode 100644 index 0000000..ee7c4a1 --- /dev/null +++ b/ladspa-lv2/RubberBandR3PitchShifter.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-2022 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 "RubberBandR3PitchShifter.h" + +#include "RubberBandStretcher.h" + +#include +#include + +using namespace RubberBand; + +using std::cout; +using std::cerr; +using std::endl; +using std::min; + +#ifdef RB_PLUGIN_LADSPA + +const char *const +RubberBandR3PitchShifter::portNamesMono[PortCountMono] = +{ + "latency", + "Cents", + "Semitones", + "Octaves", + "Formant Preserving", + "Wet-Dry Mix", + "Input", + "Output" +}; + +const char *const +RubberBandR3PitchShifter::portNamesStereo[PortCountStereo] = +{ + "latency", + "Cents", + "Semitones", + "Octaves", + "Formant Preserving", + "Wet-Dry Mix", + "Input L", + "Output L", + "Input R", + "Output R" +}; + +const LADSPA_PortDescriptor +RubberBandR3PitchShifter::portsMono[PortCountMono] = +{ + LADSPA_PORT_OUTPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO, + LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO +}; + +const LADSPA_PortDescriptor +RubberBandR3PitchShifter::portsStereo[PortCountStereo] = +{ + LADSPA_PORT_OUTPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL, + LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO, + LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO, + LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO, + LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO +}; + +const LADSPA_PortRangeHint +RubberBandR3PitchShifter::hintsMono[PortCountMono] = +{ + { 0, 0, 0 }, // latency + { LADSPA_HINT_DEFAULT_0 | // cents + LADSPA_HINT_BOUNDED_BELOW | + LADSPA_HINT_BOUNDED_ABOVE, + -100.0, 100.0 }, + { LADSPA_HINT_DEFAULT_0 | // semitones + LADSPA_HINT_BOUNDED_BELOW | + LADSPA_HINT_BOUNDED_ABOVE | + LADSPA_HINT_INTEGER, + -12.0, 12.0 }, + { LADSPA_HINT_DEFAULT_0 | // octaves + LADSPA_HINT_BOUNDED_BELOW | + LADSPA_HINT_BOUNDED_ABOVE | + LADSPA_HINT_INTEGER, + -2.0, 2.0 }, + { LADSPA_HINT_DEFAULT_0 | // formant preserving + LADSPA_HINT_BOUNDED_BELOW | + LADSPA_HINT_BOUNDED_ABOVE | + LADSPA_HINT_TOGGLED, + 0.0, 1.0 }, + { LADSPA_HINT_DEFAULT_0 | // wet-dry mix + LADSPA_HINT_BOUNDED_BELOW | + LADSPA_HINT_BOUNDED_ABOVE, + 0.0, 1.0 }, + { 0, 0, 0 }, + { 0, 0, 0 } +}; + +const LADSPA_PortRangeHint +RubberBandR3PitchShifter::hintsStereo[PortCountStereo] = +{ + { 0, 0, 0 }, // latency + { LADSPA_HINT_DEFAULT_0 | // cents + LADSPA_HINT_BOUNDED_BELOW | + LADSPA_HINT_BOUNDED_ABOVE, + -100.0, 100.0 }, + { LADSPA_HINT_DEFAULT_0 | // semitones + LADSPA_HINT_BOUNDED_BELOW | + LADSPA_HINT_BOUNDED_ABOVE | + LADSPA_HINT_INTEGER, + -12.0, 12.0 }, + { LADSPA_HINT_DEFAULT_0 | // octaves + LADSPA_HINT_BOUNDED_BELOW | + LADSPA_HINT_BOUNDED_ABOVE | + LADSPA_HINT_INTEGER, + -2.0, 2.0 }, + { LADSPA_HINT_DEFAULT_0 | // formant preserving + LADSPA_HINT_BOUNDED_BELOW | + LADSPA_HINT_BOUNDED_ABOVE | + LADSPA_HINT_TOGGLED, + 0.0, 1.0 }, + { LADSPA_HINT_DEFAULT_0 | // wet-dry mix + LADSPA_HINT_BOUNDED_BELOW | + LADSPA_HINT_BOUNDED_ABOVE, + 0.0, 1.0 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 0, 0, 0 } +}; + +const LADSPA_Properties +RubberBandR3PitchShifter::properties = LADSPA_PROPERTY_HARD_RT_CAPABLE; + +const LADSPA_Descriptor +RubberBandR3PitchShifter::ladspaDescriptorMono = +{ + 29790, // "Unique" ID + "rubberband-r3-pitchshifter-mono", // Label + properties, + "Rubber Band R3 Mono Pitch Shifter", // Name + "Breakfast Quay", + "GPL", + PortCountMono, + portsMono, + portNamesMono, + hintsMono, + nullptr, // Implementation data + instantiate, + connectPort, + activate, + run, + nullptr, // Run adding + nullptr, // Set run adding gain + deactivate, + cleanup +}; + +const LADSPA_Descriptor +RubberBandR3PitchShifter::ladspaDescriptorStereo = +{ + 97920, // "Unique" ID + "rubberband-r3-pitchshifter-stereo", // Label + properties, + "Rubber Band R3 Stereo Pitch Shifter", // Name + "Breakfast Quay", + "GPL", + PortCountStereo, + portsStereo, + portNamesStereo, + hintsStereo, + nullptr, // Implementation data + instantiate, + connectPort, + activate, + run, + nullptr, // Run adding + nullptr, // Set run adding gain + deactivate, + cleanup +}; + +const LADSPA_Descriptor * +RubberBandR3PitchShifter::getDescriptor(unsigned long index) +{ + if (index == 0) return &ladspaDescriptorMono; + if (index == 1) return &ladspaDescriptorStereo; + else return 0; +} + +#else + +const LV2_Descriptor +RubberBandR3PitchShifter::lv2DescriptorMono = +{ + "http://breakfastquay.com/rdf/lv2-rubberband-r3#mono", + instantiate, + connectPort, + activate, + run, + deactivate, + cleanup, + nullptr +}; + +const LV2_Descriptor +RubberBandR3PitchShifter::lv2DescriptorStereo = +{ + "http://breakfastquay.com/rdf/lv2-rubberband-r3#stereo", + instantiate, + connectPort, + activate, + run, + deactivate, + cleanup, + nullptr +}; + +const LV2_Descriptor * +RubberBandR3PitchShifter::getDescriptor(uint32_t index) +{ + if (index == 0) return &lv2DescriptorMono; + if (index == 1) return &lv2DescriptorStereo; + else return 0; +} + +#endif + +RubberBandR3PitchShifter::RubberBandR3PitchShifter(int sampleRate, size_t channels) : + m_latency(nullptr), + m_cents(nullptr), + m_semitones(nullptr), + m_octaves(nullptr), + m_formant(nullptr), + m_wetDry(nullptr), + m_ratio(1.0), + m_prevRatio(1.0), + m_currentFormant(false), + m_blockSize(1024), + m_reserve(8192), + m_bufsize(0), + m_minfill(0), + m_stretcher(new RubberBandStretcher + (sampleRate, channels, + RubberBandStretcher::OptionProcessRealTime | + RubberBandStretcher::OptionEngineFiner)), + m_sampleRate(sampleRate), + m_channels(channels) +{ + m_input = new float *[m_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_inptrs = 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; + + m_outputBuffer[c] = new RingBuffer(m_bufsize); + m_delayMixBuffer[c] = new RingBuffer(m_bufsize); + + m_scratch[c] = new float[m_bufsize]; + for (size_t i = 0; i < m_bufsize; ++i) { + m_scratch[c][i] = 0.f; + } + + m_inptrs[c] = 0; + } + + activateImpl(); +} + +RubberBandR3PitchShifter::~RubberBandR3PitchShifter() +{ + 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_inptrs; + delete[] m_scratch; + delete[] m_output; + delete[] m_input; +} + +#ifdef RB_PLUGIN_LADSPA + +LADSPA_Handle +RubberBandR3PitchShifter::instantiate(const LADSPA_Descriptor *desc, unsigned long rate) +{ + if (desc->PortCount == ladspaDescriptorMono.PortCount) { + return new RubberBandR3PitchShifter(rate, 1); + } else if (desc->PortCount == ladspaDescriptorStereo.PortCount) { + return new RubberBandR3PitchShifter(rate, 2); + } + return nullptr; +} + +#else + +LV2_Handle +RubberBandR3PitchShifter::instantiate(const LV2_Descriptor *desc, double rate, + const char *, const LV2_Feature *const *) +{ + if (rate < 1.0) { + std::cerr << "RubberBandR3PitchShifter::instantiate: invalid sample rate " + << rate << " provided" << std::endl; + return nullptr; + } + size_t srate = size_t(round(rate)); + if (std::string(desc->URI) == lv2DescriptorMono.URI) { + return new RubberBandR3PitchShifter(srate, 1); + } else if (std::string(desc->URI) == lv2DescriptorStereo.URI) { + return new RubberBandR3PitchShifter(srate, 2); + } else { + std::cerr << "RubberBandR3PitchShifter::instantiate: unrecognised URI " + << desc->URI << " requested" << std::endl; + return nullptr; + } +} + +#endif + +#ifdef RB_PLUGIN_LADSPA +void +RubberBandR3PitchShifter::connectPort(LADSPA_Handle handle, + unsigned long port, LADSPA_Data *location) +#else +void +RubberBandR3PitchShifter::connectPort(LV2_Handle handle, + uint32_t port, void *location) +#endif +{ + RubberBandR3PitchShifter *shifter = (RubberBandR3PitchShifter *)handle; + + float **ports[PortCountStereo] = { + &shifter->m_latency, + &shifter->m_cents, + &shifter->m_semitones, + &shifter->m_octaves, + &shifter->m_formant, + &shifter->m_wetDry, + &shifter->m_input[0], + &shifter->m_output[0], + &shifter->m_input[1], + &shifter->m_output[1] + }; + + if (shifter->m_channels == 1) { + if (port >= PortCountMono) return; + } else { + if (port >= PortCountStereo) return; + } + + *ports[port] = (float *)location; + + if (shifter->m_latency) { + *(shifter->m_latency) = shifter->getLatency(); + } +} + +#ifdef RB_PLUGIN_LADSPA +void +RubberBandR3PitchShifter::activate(LADSPA_Handle handle) +#else +void +RubberBandR3PitchShifter::activate(LV2_Handle handle) +#endif +{ + RubberBandR3PitchShifter *shifter = (RubberBandR3PitchShifter *)handle; + shifter->activateImpl(); +} + +#ifdef RB_PLUGIN_LADSPA +void +RubberBandR3PitchShifter::run(LADSPA_Handle handle, unsigned long samples) +#else +void +RubberBandR3PitchShifter::run(LV2_Handle handle, uint32_t samples) +#endif +{ + RubberBandR3PitchShifter *shifter = (RubberBandR3PitchShifter *)handle; + shifter->runImpl(samples); +} + +#ifdef RB_PLUGIN_LADSPA +void +RubberBandR3PitchShifter::deactivate(LADSPA_Handle handle) +#else +void +RubberBandR3PitchShifter::deactivate(LV2_Handle handle) +#endif +{ + activate(handle); // both functions just reset the plugin +} + +#ifdef RB_PLUGIN_LADSPA +void +RubberBandR3PitchShifter::cleanup(LADSPA_Handle handle) +#else +void +RubberBandR3PitchShifter::cleanup(LV2_Handle handle) +#endif +{ + delete (RubberBandR3PitchShifter *)handle; +} + +int +RubberBandR3PitchShifter::getLatency() const +{ + return m_reserve; +} + +void +RubberBandR3PitchShifter::activateImpl() +{ + updateRatio(); + m_prevRatio = m_ratio; + m_stretcher->reset(); + m_stretcher->setPitchScale(m_ratio); + + for (size_t c = 0; c < m_channels; ++c) { + m_outputBuffer[c]->reset(); + } + + 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; + + m_stretcher->process(m_scratch, m_reserve, false); +} + +void +RubberBandR3PitchShifter::updateRatio() +{ + // The octaves, semitones, and cents parameters are supposed to be + // integral: we want to enforce that, just to avoid + // inconsistencies between hosts if some respect the hints more + // than others + +#ifdef RB_PLUGIN_LADSPA + + // But we don't want to change the long-standing behaviour of the + // LADSPA plugin, so let's leave this as-is and only do "the right + // thing" for LV2 + double oct = (m_octaves ? *m_octaves : 0.0); + oct += (m_semitones ? *m_semitones : 0.0) / 12; + oct += (m_cents ? *m_cents : 0.0) / 1200; + m_ratio = pow(2.0, oct); + +#else + + // LV2 + + double octaves = round(m_octaves ? *m_octaves : 0.0); + if (octaves < -2.0) octaves = -2.0; + if (octaves > 2.0) octaves = 2.0; + + double semitones = round(m_semitones ? *m_semitones : 0.0); + if (semitones < -12.0) semitones = -12.0; + if (semitones > 12.0) semitones = 12.0; + + double cents = round(m_cents ? *m_cents : 0.0); + if (cents < -100.0) cents = -100.0; + if (cents > 100.0) cents = 100.0; + + m_ratio = pow(2.0, + octaves + + semitones / 12.0 + + cents / 1200.0); +#endif +} + +void +RubberBandR3PitchShifter::updateFormant() +{ + if (!m_formant) return; + + bool f = (*m_formant > 0.5f); + if (f == m_currentFormant) return; + + RubberBandStretcher *s = m_stretcher; + + s->setFormantOption(f ? + RubberBandStretcher::OptionFormantPreserved : + RubberBandStretcher::OptionFormantShifted); + + m_currentFormant = f; +} + +void +RubberBandR3PitchShifter::runImpl(uint32_t insamples) +{ + for (size_t c = 0; c < m_channels; ++c) { + m_delayMixBuffer[c]->write(m_input[c], insamples); + } + + size_t offset = 0; + + // We have to break up the input into chunks like this because + // insamples could be arbitrarily large and our output buffer is + // of limited size + + while (offset < insamples) { + + size_t block = m_blockSize; + if (offset + block > insamples) { + block = insamples - offset; + } + + runImpl(block, offset); + + offset += block; + } + + float mix = 0.0; + if (m_wetDry) mix = *m_wetDry; + + for (size_t c = 0; c < m_channels; ++c) { + if (mix > 0.0) { + for (size_t 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 +RubberBandR3PitchShifter::runImpl(uint32_t insamples, uint32_t offset) +{ + updateRatio(); + if (m_ratio != m_prevRatio) { + m_stretcher->setPitchScale(m_ratio); + m_prevRatio = m_ratio; + } + + if (m_latency) { + *m_latency = getLatency(); + } + + updateFormant(); + + const int samples = insamples; + int processed = 0; + size_t outTotal = 0; + + while (processed < samples) { + + // never feed more than the minimum necessary number of + // samples at a time; ensures nothing will overflow internally + // and we don't need to call setMaxProcessSize + + int toCauseProcessing = m_stretcher->getSamplesRequired(); + int inchunk = min(samples - processed, toCauseProcessing); + + for (size_t c = 0; c < m_channels; ++c) { + m_inptrs[c] = &(m_input[c][offset + processed]); + } + + m_stretcher->process(m_inptrs, inchunk, false); + + processed += inchunk; + + int avail = m_stretcher->available(); + int writable = m_outputBuffer[0]->getWriteSpace(); + + int outchunk = avail; + if (outchunk > writable) { + cerr << "RubberBandR3PitchShifter::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; + + for (size_t c = 0; c < m_channels; ++c) { + m_outputBuffer[c]->write(m_scratch[c], actual); + } + } + + for (size_t c = 0; c < m_channels; ++c) { + int toRead = m_outputBuffer[c]->getReadSpace(); + if (toRead < samples && c == 0) { + cerr << "RubberBandR3PitchShifter::runImpl: buffer underrun: required = " << samples << ", available = " << toRead << endl; + } + int chunk = min(toRead, samples); + m_outputBuffer[c]->read(&(m_output[c][offset]), chunk); + } + + 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-lv2/RubberBandR3PitchShifter.h b/ladspa-lv2/RubberBandR3PitchShifter.h new file mode 100644 index 0000000..52cfb60 --- /dev/null +++ b/ladspa-lv2/RubberBandR3PitchShifter.h @@ -0,0 +1,149 @@ +/* -*- 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-2022 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 RUBBERBAND_R3_PITCH_SHIFTER_H +#define RUBBERBAND_R3_PITCH_SHIFTER_H + +#ifdef RB_PLUGIN_LADSPA +#ifdef RB_PLUGIN_LV2 +#error "Only one of RB_PLUGIN_LADSPA and RB_PLUGIN_LV2 may be defined at once" +#endif +#else +#ifndef RB_PLUGIN_LV2 +#error "Including code must define either RB_PLUGIN_LADSPA or RB_PLUGIN_LV2" +#endif +#endif + +#ifdef RB_PLUGIN_LADSPA +#include +#else +#include +#endif + +#include "common/RingBuffer.h" + +namespace RubberBand { +class RubberBandStretcher; +} + +class RubberBandR3PitchShifter +{ +public: +#ifdef RB_PLUGIN_LADSPA + static const LADSPA_Descriptor *getDescriptor(unsigned long index); +#else + static const LV2_Descriptor *getDescriptor(uint32_t index); +#endif + +protected: + RubberBandR3PitchShifter(int sampleRate, size_t channels); + ~RubberBandR3PitchShifter(); + + enum { + LatencyPort = 0, + CentsPort = 1, + SemitonesPort = 2, + OctavesPort = 3, + FormantPort = 4, + WetDryPort = 5, + InputPort1 = 6, + OutputPort1 = 7, + PortCountMono = OutputPort1 + 1, + InputPort2 = 8, + OutputPort2 = 9, + PortCountStereo = OutputPort2 + 1 + }; + +#ifdef RB_PLUGIN_LADSPA + static const char *const portNamesMono[PortCountMono]; + static const LADSPA_PortDescriptor portsMono[PortCountMono]; + static const LADSPA_PortRangeHint hintsMono[PortCountMono]; + + static const char *const portNamesStereo[PortCountStereo]; + static const LADSPA_PortDescriptor portsStereo[PortCountStereo]; + static const LADSPA_PortRangeHint hintsStereo[PortCountStereo]; + + static const LADSPA_Properties properties; + + static const LADSPA_Descriptor ladspaDescriptorMono; + static const LADSPA_Descriptor ladspaDescriptorStereo; + + static LADSPA_Handle instantiate(const LADSPA_Descriptor *, unsigned long); + static void connectPort(LADSPA_Handle, unsigned long, LADSPA_Data *); + static void activate(LADSPA_Handle); + static void run(LADSPA_Handle, unsigned long); + static void deactivate(LADSPA_Handle); + static void cleanup(LADSPA_Handle); + +#else + + static const LV2_Descriptor lv2DescriptorMono; + static const LV2_Descriptor lv2DescriptorStereo; + + static LV2_Handle instantiate(const LV2_Descriptor *, double, + const char *, const LV2_Feature *const *); + static void connectPort(LV2_Handle, uint32_t, void *); + static void activate(LV2_Handle); + static void run(LV2_Handle, uint32_t); + static void deactivate(LV2_Handle); + static void cleanup(LV2_Handle); + +#endif + + void activateImpl(); + void runImpl(uint32_t count); + void runImpl(uint32_t count, uint32_t offset); + void updateRatio(); + void updateFormant(); + + int getLatency() const; + + float **m_input; + float **m_output; + float *m_latency; + float *m_cents; + float *m_semitones; + float *m_octaves; + float *m_formant; + float *m_wetDry; + double m_ratio; + double m_prevRatio; + bool m_currentFormant; + + 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; + float **m_inptrs; + + int m_sampleRate; + size_t m_channels; +}; + + +#endif diff --git a/ladspa-lv2/ladspa-rubberband.cat b/ladspa-lv2/ladspa-rubberband.cat index 438e9a3..907081f 100644 --- a/ladspa-lv2/ladspa-rubberband.cat +++ b/ladspa-lv2/ladspa-rubberband.cat @@ -1,2 +1,4 @@ ladspa:ladspa-rubberband:rubberband-pitchshifter-mono::Frequency > Pitch shifters ladspa:ladspa-rubberband:rubberband-pitchshifter-stereo::Frequency > Pitch shifters +ladspa:ladspa-rubberband:rubberband-r3-pitchshifter-mono::Frequency > Pitch shifters +ladspa:ladspa-rubberband:rubberband-r3-pitchshifter-stereo::Frequency > Pitch shifters diff --git a/ladspa-lv2/libmain-ladspa.cpp b/ladspa-lv2/libmain-ladspa.cpp index 3d22289..518b87f 100644 --- a/ladspa-lv2/libmain-ladspa.cpp +++ b/ladspa-lv2/libmain-ladspa.cpp @@ -24,6 +24,7 @@ #define RB_PLUGIN_LADSPA 1 #undef RB_PLUGIN_LV2 #include "RubberBandPitchShifter.cpp" +#include "RubberBandR3PitchShifter.cpp" #include @@ -31,7 +32,11 @@ extern "C" { const LADSPA_Descriptor *ladspa_descriptor(unsigned long index) { - return RubberBandPitchShifter::getDescriptor(index); + if (index < 2) { + return RubberBandPitchShifter::getDescriptor(index); + } else { + return RubberBandR3PitchShifter::getDescriptor(index - 2); + } } } diff --git a/ladspa-lv2/libmain-lv2.cpp b/ladspa-lv2/libmain-lv2.cpp index 3e41ffb..57752f6 100644 --- a/ladspa-lv2/libmain-lv2.cpp +++ b/ladspa-lv2/libmain-lv2.cpp @@ -24,6 +24,7 @@ #define RB_PLUGIN_LV2 1 #undef RB_PLUGIN_LADSPA #include "RubberBandPitchShifter.cpp" +#include "RubberBandR3PitchShifter.cpp" #include @@ -32,7 +33,11 @@ extern "C" { LV2_SYMBOL_EXPORT const LV2_Descriptor *lv2_descriptor(uint32_t index) { - return RubberBandPitchShifter::getDescriptor(index); + if (index < 2) { + return RubberBandPitchShifter::getDescriptor(index); + } else { + return RubberBandR3PitchShifter::getDescriptor(index - 2); + } } } diff --git a/ladspa-lv2/rubberband.lv2/lv2-rubberband.ttl b/ladspa-lv2/rubberband.lv2/lv2-rubberband.ttl index d09d7aa..2f8a516 100644 --- a/ladspa-lv2/rubberband.lv2/lv2-rubberband.ttl +++ b/ladspa-lv2/rubberband.lv2/lv2-rubberband.ttl @@ -78,6 +78,16 @@ lv2:maximum 1 ; lv2:portProperty lv2:integer, lv2:toggled . +:formantPortR3 + a lv2:ControlPort, lv2:InputPort ; + lv2:index 4 ; + lv2:symbol "formant" ; + lv2:name "Formant Preserving" ; + lv2:default 0 ; + lv2:minimum 0 ; + lv2:maximum 1 ; + lv2:portProperty lv2:integer, lv2:toggled . + :wetDryPort a lv2:ControlPort, lv2:InputPort ; lv2:index 6 ; @@ -87,6 +97,15 @@ lv2:minimum 0 ; lv2:maximum 1 . +:wetDryPortR3 + a lv2:ControlPort, lv2:InputPort ; + lv2:index 5 ; + lv2:symbol "wetdry" ; + lv2:name "Wet-Dry Mix" ; + lv2:default 0 ; + lv2:minimum 0 ; + lv2:maximum 1 . + rubberband:mono_in_group a pg:MonoGroup, pg:InputGroup ; lv2:symbol "mono_in" ; @@ -146,6 +165,44 @@ rubberband:mono lv2:designation pg:center ; ] . +rubberband:r3mono + a doap:Project, lv2:Plugin, lv2:PitchPlugin ; + doap:name "Rubber Band R3 Mono Pitch Shifter" ; + doap:license ; + foaf:maker :maker ; + doap:developer :maker ; + doap:maintainer :maker ; + # Minor version will be 2x the Rubber Band API minor version, + # but this is an initial test release, so 0 + lv2:minorVersion 0 ; + lv2:microVersion 0 ; + lv2:optionalFeature lv2:hardRTCapable ; + pg:mainInput rubberband:mono_in_group ; + pg:mainOutput rubberband:mono_out_group ; + dc:replaces ; + lv2:port :latencyPort , + :centsPort , + :semitonesPort , + :octavesPort , + :formantPortR3 , + :wetDryPortR3 , + [ a lv2:AudioPort, lv2:InputPort ; + lv2:index 6 ; + lv2:symbol "input" ; + lv2:name "Input" ; + lv2:shortName "Input" ; + pg:group rubberband:mono_in_group ; + lv2:designation pg:center ; + ], [ + a lv2:AudioPort, lv2:OutputPort ; + lv2:index 7 ; + lv2:symbol "output" ; + lv2:name "Output" ; + lv2:shortName "Output" ; + pg:group rubberband:mono_out_group ; + lv2:designation pg:center ; + ] . + rubberband:stereo a doap:Project, lv2:Plugin, lv2:PitchPlugin ; doap:name "Rubber Band Stereo Pitch Shifter" ; @@ -200,3 +257,56 @@ rubberband:stereo lv2:designation pg:right ; ] . +rubberband:r3stereo + a doap:Project, lv2:Plugin, lv2:PitchPlugin ; + doap:name "Rubber Band R3 Stereo Pitch Shifter" ; + doap:license ; + foaf:maker :maker ; + doap:developer :maker ; + doap:maintainer :maker ; + # Minor version will be 2x the Rubber Band API minor version, + # but this is an initial test release, so 0 + lv2:minorVersion 0 ; + lv2:microVersion 0 ; + lv2:optionalFeature lv2:hardRTCapable ; + pg:mainInput rubberband:stereo_in_group ; + pg:mainOutput rubberband:stereo_out_group ; + dc:replaces ; + lv2:port :latencyPort , + :centsPort , + :semitonesPort , + :octavesPort , + :formantPortR3 , + :wetDryPortR3 , + [ a lv2:AudioPort, lv2:InputPort ; + lv2:index 6 ; + lv2:symbol "input_l" ; + lv2:name "Input L" ; + lv2:shortName "Input L" ; + pg:group rubberband:stereo_in_group ; + lv2:designation pg:left ; + ], [ + a lv2:AudioPort, lv2:OutputPort ; + lv2:index 7 ; + lv2:symbol "output_l" ; + lv2:name "Output L" ; + lv2:shortName "Output L" ; + pg:group rubberband:stereo_out_group ; + lv2:designation pg:left ; + ], [ a lv2:AudioPort, lv2:InputPort ; + lv2:index 8 ; + lv2:symbol "input_r" ; + lv2:name "Input R" ; + lv2:shortName "Input R" ; + pg:group rubberband:stereo_in_group ; + lv2:designation pg:right ; + ], [ + a lv2:AudioPort, lv2:OutputPort ; + lv2:index 9 ; + lv2:symbol "output_r" ; + lv2:name "Output R" ; + lv2:shortName "Output R" ; + pg:group rubberband:stereo_out_group ; + lv2:designation pg:right ; + ] . + diff --git a/ladspa-lv2/rubberband.lv2/manifest.ttl b/ladspa-lv2/rubberband.lv2/manifest.ttl index 5931138..ccba9dd 100644 --- a/ladspa-lv2/rubberband.lv2/manifest.ttl +++ b/ladspa-lv2/rubberband.lv2/manifest.ttl @@ -7,8 +7,18 @@ rubberband:mono lv2:binary ; rdfs:seeAlso . +rubberband:r3mono + a lv2:Plugin ; + lv2:binary ; + rdfs:seeAlso . + rubberband:stereo a lv2:Plugin ; lv2:binary ; rdfs:seeAlso . +rubberband:r3stereo + a lv2:Plugin ; + lv2:binary ; + rdfs:seeAlso . + diff --git a/main/main.cpp b/main/main.cpp index 78e841d..3309490 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -33,24 +33,26 @@ #include -#include "../src/system/sysutils.h" +#include "../src/common/sysutils.h" +#include "../src/common/Profiler.h" #ifdef _MSC_VER -#include "../src/getopt/getopt.h" +#include "../src/ext/getopt/getopt.h" #else #include #include #include #endif -#include "../src/base/Profiler.h" - #ifdef _WIN32 using RubberBand::gettimeofday; #endif #ifdef _MSC_VER -using RubberBand::usleep; +#include +static void usleep(unsigned long usec) { + ::Sleep(usec == 0 ? 0 : usec < 1000 ? 1 : usec / 1000); +} #define strdup _strdup #endif @@ -82,15 +84,13 @@ double tempo_convert(const char *str) int main(int argc, char **argv) { - int c; - double ratio = 1.0; double duration = 0.0; double pitchshift = 0.0; double frequencyshift = 1.0; int debug = 0; bool realtime = false; - bool precise = true; + bool precisiongiven = false; int threading = 0; bool lamination = true; bool longwin = false; @@ -101,7 +101,10 @@ int main(int argc, char **argv) bool together = false; bool crispchanged = false; int crispness = -1; + bool faster = false; + bool finer = false; bool help = false; + bool fullHelp = false; bool version = false; bool quiet = false; @@ -126,11 +129,22 @@ int main(int argc, char **argv) bool ignoreClipping = false; + std::string myName(argv[0]); + + bool isR3 = + ((myName.size() > 3 && + myName.substr(myName.size() - 3, 3) == "-r3") || + (myName.size() > 7 && + myName.substr(myName.size() - 7, 7) == "-r3.exe") || + (myName.size() > 7 && + myName.substr(myName.size() - 7, 7) == "-R3.EXE")); + while (1) { int optionIndex = 0; static struct option longOpts[] = { { "help", 0, 0, 'h' }, + { "full-help", 0, 0, 'H' }, { "version", 0, 0, 'V' }, { "time", 1, 0, 't' }, { "tempo", 1, 0, 'T' }, @@ -146,10 +160,10 @@ int main(int argc, char **argv) { "formant", 0, 0, 'F' }, { "no-threads", 0, 0, '0' }, { "no-transients", 0, 0, '1' }, - { "no-lamination", 0, 0, '2' }, + { "no-lamination", 0, 0, '.' }, { "centre-focus", 0, 0, '7' }, - { "window-long", 0, 0, '3' }, - { "window-short", 0, 0, '4' }, + { "window-long", 0, 0, '>' }, + { "window-short", 0, 0, '<' }, { "bl-transients", 0, 0, '8' }, { "detector-perc", 0, 0, '5' }, { "detector-soft", 0, 0, '6' }, @@ -161,16 +175,19 @@ int main(int argc, char **argv) { "freqmap", 1, 0, 'Q' }, { "pitchmap", 1, 0, 'C' }, { "ignore-clipping", 0, 0, 'i' }, + { "fast", 0, 0, '2' }, + { "fine", 0, 0, '3' }, { 0, 0, 0, 0 } }; - c = getopt_long(argc, argv, - "t:p:d:RLPFc:f:T:D:qhVM:", - longOpts, &optionIndex); - if (c == -1) break; + int optionChar = getopt_long(argc, argv, + "t:p:d:RLPFc:f:T:D:qhHVM:23", + longOpts, &optionIndex); + if (optionChar == -1) break; - switch (c) { + switch (optionChar) { case 'h': help = true; break; + case 'H': fullHelp = true; break; case 'V': version = true; break; case 't': ratio *= atof(optarg); haveRatio = true; break; case 'T': ratio *= tempo_convert(optarg); haveRatio = true; break; @@ -179,15 +196,15 @@ int main(int argc, char **argv) case 'f': frequencyshift = atof(optarg); haveRatio = true; break; case 'd': debug = atoi(optarg); break; case 'R': realtime = true; break; - case 'L': precise = false; break; - case 'P': precise = true; break; + case 'L': precisiongiven = true; break; + case 'P': precisiongiven = true; break; case 'F': formant = true; break; case '0': threading = 1; break; case '@': threading = 2; break; case '1': transients = NoTransients; crispchanged = true; break; - case '2': lamination = false; crispchanged = true; break; - case '3': longwin = true; crispchanged = true; break; - case '4': shortwin = true; crispchanged = true; break; + case '.': lamination = false; crispchanged = true; break; + case '>': longwin = true; crispchanged = true; break; + case '<': shortwin = true; crispchanged = true; break; case '5': detector = PercussiveDetector; crispchanged = true; break; case '6': detector = SoftDetector; crispchanged = true; break; case '7': together = true; break; @@ -200,6 +217,8 @@ int main(int argc, char **argv) case 'Q': freqMapFile = optarg; freqOrPitchMapSpecified = true; break; case 'C': pitchMapFile = optarg; freqOrPitchMapSpecified = true; break; case 'i': ignoreClipping = true; break; + case '2': faster = true; break; + case '3': finer = true; break; default: help = true; break; } } @@ -218,15 +237,15 @@ int main(int argc, char **argv) realtime = true; } - if (help || !haveRatio || optind + 2 != argc) { + if (help || fullHelp || !haveRatio || optind + 2 != argc) { cerr << endl; cerr << "Rubber Band" << endl; cerr << "An audio time-stretching and pitch-shifting library and utility program." << endl; cerr << "Copyright 2007-2022 Particular Programs Ltd." << endl; cerr << endl; - cerr << " Usage: " << argv[0] << " [options] " << endl; + cerr << " Usage: " << myName << " [options] " << endl; cerr << endl; - cerr << "You must specify at least one of the following time and pitch ratio options." << endl; + cerr << "You must specify at least one of the following time and pitch ratio options:" << endl; cerr << endl; cerr << " -t, --time Stretch to X times original duration, or" << endl; cerr << " -T, --tempo Change tempo by multiple X (same as --time 1/X), or" << endl; @@ -237,7 +256,7 @@ int main(int argc, char **argv) cerr << " -f, --frequency Change frequency by multiple X" << endl; cerr << endl; cerr << "The following options provide ways of making the time and frequency ratios" << endl; - cerr << "change during the audio." << endl; + cerr << "change during the audio:" << endl; cerr << endl; cerr << " -M, --timemap Use file F as the source for time map" << endl; cerr << endl; @@ -265,52 +284,86 @@ int main(int argc, char **argv) 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 << "The following options affect the sound manipulation and quality:" << endl; + cerr << endl; + cerr << " -2, --fast Use the R2 (faster) engine" << endl; + cerr << endl; + cerr << " This is the default (for backward compatibility) when this tool is invoked" << endl; + cerr << " as \"rubberband\". It was the only engine available in versions prior to v3.0." << endl; + cerr << endl; + cerr << " -3, --fine Use the R3 (finer) engine" << endl; + cerr << endl; + cerr << " This is the default when this tool is invoked as \"rubberband-r3\". It almost" << endl; + cerr << " always produces better results than the R2 engine, but with significantly" << endl; + cerr << " higher CPU load." << 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 << 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 << " This option attempts to keep the formant envelope unchanged when changing" << endl; + cerr << " the pitch, retaining the original timbre of vocals and instruments in a" << endl; + cerr << " recognisable way." << 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; - cerr << " -R, --realtime Select realtime mode (implies --no-threads)" << endl; - cerr << " --no-threads No extra threads regardless of CPU and channel count" << endl; - cerr << " --threads Assume multi-CPU even if only one CPU is identified" << endl; - cerr << " --no-transients Disable phase resynchronisation at transients" << endl; - cerr << " --bl-transients Band-limit phase resync to extreme frequencies" << endl; - cerr << " --no-lamination Disable phase lamination" << endl; - cerr << " --window-long Use longer processing window (actual size may vary)" << endl; - cerr << " --window-short Use shorter processing window" << endl; - cerr << " --smoothing Apply window presum and time-domain smoothing" << endl; - cerr << " --detector-perc Use percussive transient detector (as in pre-1.5)" << endl; - cerr << " --detector-soft Use soft transient detector" << endl; - 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; + if (fullHelp || !isR3) { + cerr << " -c, --crisp Crispness (N = 0,1,2,3,4,5,6); default 5" << endl; + cerr << endl; + cerr << " This option only has an effect when using the R2 (faster) engine. See below" << endl; + cerr << " for details of the different levels." << endl; + cerr << endl; + } + if (fullHelp) { + 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 << endl; + cerr << " -R, --realtime Select realtime mode (implies --no-threads)." << endl; + cerr << " This utility does not do realtime stream processing;" << endl; + cerr << " the option merely selects realtime mode for the" << endl; + cerr << " stretcher it uses" << endl; + cerr << " --no-threads No extra threads regardless of CPU and channel count" << endl; + cerr << " --threads Assume multi-CPU even if only one CPU is identified" << endl; + cerr << " --no-transients Disable phase resynchronisation at transients" << endl; + cerr << " --bl-transients Band-limit phase resync to extreme frequencies" << endl; + cerr << " --no-lamination Disable phase lamination" << endl; + cerr << " --window-long Use longer processing window (actual size may vary)" << endl; + cerr << " --window-short Use shorter processing window" << endl; + cerr << " --smoothing Apply window presum and time-domain smoothing" << endl; + cerr << " --detector-perc Use percussive transient detector (as in pre-1.5)" << endl; + cerr << " --detector-soft Use soft transient detector" << endl; + 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 << " -L, --loose [Accepted for compatibility but ignored; always off]" << endl; + cerr << " -P, --precise [Accepted for compatibility but ignored; always on]" << 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; + cerr << endl; + } + cerr << "The following options are for output control and administration:" << 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; cerr << " -q, --quiet Suppress progress output" << endl; - cerr << endl; cerr << " -V, --version Show version number and exit" << endl; - cerr << " -h, --help Show this help" << endl; - cerr << endl; - cerr << "\"Crispness\" levels:" << endl; - cerr << " -c 0 equivalent to --no-transients --no-lamination --window-long" << endl; - cerr << " -c 1 equivalent to --detector-soft --no-lamination --window-long (for piano)" << endl; - cerr << " -c 2 equivalent to --no-transients --no-lamination" << endl; - cerr << " -c 3 equivalent to --no-transients" << endl; - cerr << " -c 4 equivalent to --bl-transients" << endl; - cerr << " -c 5 default processing options" << endl; - cerr << " -c 6 equivalent to --no-lamination --window-short (may be good for drums)" << endl; + cerr << " -h, --help Show the normal help output" << endl; + cerr << " -H, --full-help Show the full help output" << endl; cerr << endl; + if (fullHelp) { + cerr << "\"Crispness\" levels:" << endl; + cerr << " -c 0 equivalent to --no-transients --no-lamination --window-long" << endl; + cerr << " -c 1 equivalent to --detector-soft --no-lamination --window-long (for piano)" << endl; + cerr << " -c 2 equivalent to --no-transients --no-lamination" << endl; + cerr << " -c 3 equivalent to --no-transients" << endl; + cerr << " -c 4 equivalent to --bl-transients" << endl; + cerr << " -c 5 default processing options" << endl; + cerr << " -c 6 equivalent to --no-lamination --window-short (may be good for drums)" << endl; + cerr << endl; + } else { + cerr << "Numerous other options are available, mostly for tuning the behaviour of" << endl; + cerr << "the R2 engine. Run \"" << myName << " --full-help\" for details." << endl; + cerr << endl; + } return 2; } @@ -318,6 +371,23 @@ int main(int argc, char **argv) cerr << "ERROR: Invalid time ratio " << ratio << endl; return 1; } + + if (faster && finer) { + cerr << "WARNING: Both fast (R2) and fine (R3) engines selected, will use default for" << endl; + cerr << " this tool (" << (isR3 ? "fine" : "fast") << ")" << endl; + faster = false; + finer = false; + } + + if (isR3) { + if (!faster) { + finer = true; + } + } else { + if (!finer) { + faster = true; + } + } if (crispness >= 0 && crispchanged) { cerr << "WARNING: Both crispness option and transients, lamination or window options" << endl; @@ -330,6 +400,11 @@ int main(int argc, char **argv) hqpitch = false; } + if (precisiongiven) { + cerr << "NOTE: The -L/--loose and -P/--precise options are both ignored -- precise" << endl; + cerr << " became the default in v1.6 and loose was removed in v3.0" << endl; + } + switch (crispness) { case -1: crispness = 5; break; case 0: detector = CompoundDetector; transients = NoTransients; lamination = false; longwin = true; shortwin = false; break; @@ -342,17 +417,22 @@ int main(int argc, char **argv) }; if (!quiet) { - cerr << "Using crispness level: " << crispness << " ("; - switch (crispness) { - case 0: cerr << "Mushy"; break; - case 1: cerr << "Piano"; break; - case 2: cerr << "Smooth"; break; - case 3: cerr << "Balanced multitimbral mixture"; break; - case 4: cerr << "Unpitched percussion with stable notes"; break; - case 5: cerr << "Crisp monophonic instrumental"; break; - case 6: cerr << "Unpitched solo percussion"; break; + if (finer) { + cerr << "Using R3 (finer) engine" << endl; + } else { + cerr << "Using R2 (faster) engine" << endl; + cerr << "Using crispness level: " << crispness << " ("; + switch (crispness) { + case 0: cerr << "Mushy"; break; + case 1: cerr << "Piano"; break; + case 2: cerr << "Smooth"; break; + case 3: cerr << "Balanced multitimbral mixture"; break; + case 4: cerr << "Unpitched percussion with stable notes"; break; + case 5: cerr << "Crisp monophonic instrumental"; break; + case 6: cerr << "Unpitched solo percussion"; break; + } + cerr << ")" << endl; } - cerr << ")" << endl; } std::map timeMap; @@ -492,8 +572,11 @@ int main(int argc, char **argv) } RubberBandStretcher::Options options = 0; + if (finer) { + options = RubberBandStretcher::OptionEngineFiner; + } + if (realtime) options |= RubberBandStretcher::OptionProcessRealTime; - if (precise) options |= RubberBandStretcher::OptionStretchPrecise; if (!lamination) options |= RubberBandStretcher::OptionPhaseIndependent; if (longwin) options |= RubberBandStretcher::OptionWindowLong; if (shortwin) options |= RubberBandStretcher::OptionWindowShort; @@ -782,19 +865,21 @@ int main(int argc, char **argv) } 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; + cerr << "NOTE: Clipping detected at output sample " + << countOut << ", but not reducing gain as it would " + << "mean dropping below minimum " << mingain << endl; gain = mingain; ignoreClipping = true; + } else { + if (!quiet) { + cerr << "NOTE: Clipping detected at output sample " + << countOut << ", restarting with " + << "reduced gain of " << gain + << " (supply --ignore-clipping to avoid this)" + << endl; + } } successful = false; break; @@ -900,7 +985,11 @@ int main(int argc, char **argv) if (!quiet) { - cerr << "in: " << countIn << ", out: " << countOut << ", ratio: " << float(countOut)/float(countIn) << ", ideal output: " << lrint(countIn * ratio) << ", error: " << abs(lrint(countIn * ratio) - int(countOut)) << endl; + cerr << "in: " << countIn << ", out: " << countOut + << ", ratio: " << float(countOut)/float(countIn) + << ", ideal output: " << lrint(countIn * ratio) + << ", error: " << abs(lrint(countIn * ratio) - int(countOut)) + << endl; #ifdef _WIN32 RubberBand:: @@ -916,7 +1005,9 @@ 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: " << countIn/sec << ", out frames/sec: " << countOut/sec << endl; + cerr << "elapsed time: " << sec << " sec, in frames/sec: " + << int64_t(countIn/sec) << ", out frames/sec: " + << int64_t(countOut/sec) << endl; } RubberBand::Profiler::dump(); diff --git a/meson.build b/meson.build index 3f9a9ec..38e7b1d 100644 --- a/meson.build +++ b/meson.build @@ -2,10 +2,11 @@ project( 'Rubber Band Library', 'c', 'cpp', - version: '2.0.2', + version: '3.0.0-beta3', license: 'GPL-2.0-or-later', default_options: [ - 'cpp_std=c++14', + 'cpp_std=c++11', + 'warning_level=3', 'buildtype=release', 'default_library=both', 'b_ndebug=if-release', @@ -33,23 +34,23 @@ public_headers = [ library_sources = [ 'src/rubberband-c.cpp', 'src/RubberBandStretcher.cpp', - 'src/StretcherProcess.cpp', - 'src/StretchCalculator.cpp', - 'src/base/Profiler.cpp', - 'src/dsp/AudioCurveCalculator.cpp', - 'src/audiocurves/CompoundAudioCurve.cpp', - 'src/audiocurves/SpectralDifferenceAudioCurve.cpp', - 'src/audiocurves/HighFrequencyAudioCurve.cpp', - 'src/audiocurves/SilentAudioCurve.cpp', - 'src/audiocurves/ConstantAudioCurve.cpp', - 'src/audiocurves/PercussiveAudioCurve.cpp', - 'src/dsp/Resampler.cpp', - 'src/dsp/FFT.cpp', - 'src/system/Allocators.cpp', - 'src/system/sysutils.cpp', - 'src/system/Thread.cpp', - 'src/StretcherChannelData.cpp', - 'src/StretcherImpl.cpp', + 'src/faster/AudioCurveCalculator.cpp', + 'src/faster/CompoundAudioCurve.cpp', + 'src/faster/HighFrequencyAudioCurve.cpp', + 'src/faster/SilentAudioCurve.cpp', + 'src/faster/PercussiveAudioCurve.cpp', + 'src/faster/R2Stretcher.cpp', + 'src/faster/StretcherChannelData.cpp', + 'src/faster/StretcherProcess.cpp', + 'src/common/Allocators.cpp', + 'src/common/FFT.cpp', + 'src/common/Log.cpp', + 'src/common/Profiler.cpp', + 'src/common/Resampler.cpp', + 'src/common/StretchCalculator.cpp', + 'src/common/sysutils.cpp', + 'src/common/Thread.cpp', + 'src/finer/R3Stretcher.cpp', ] jni_sources = [ @@ -66,8 +67,8 @@ program_sources = [ if system == 'windows' program_sources += [ - 'src/getopt/getopt.c', - 'src/getopt/getopt_long.c' + 'src/ext/getopt/getopt.c', + 'src/ext/getopt/getopt_long.c' ] endif @@ -84,6 +85,19 @@ lv2_sources = [ 'ladspa-lv2/libmain-lv2.cpp', ] +unit_test_sources = [ + 'src/test/TestAllocators.cpp', + 'src/test/TestFFT.cpp', + 'src/test/TestResampler.cpp', + 'src/test/TestVectorOpsComplex.cpp', + 'src/test/TestVectorOps.cpp', + 'src/test/TestSignalBits.cpp', + 'src/test/TestStretchCalculator.cpp', + 'src/test/TestStretcher.cpp', + 'src/test/TestBinClassifier.cpp', + 'src/test/test.cpp', +] + general_include_dirs = [ 'rubberband', 'src', @@ -101,6 +115,7 @@ fftw3_dep = dependency('fftw3', version: '>= 3.0.0', required: false) samplerate_dep = dependency('samplerate', version: '>= 0.1.8', required: false) sndfile_dep = dependency('sndfile', version: '>= 1.0.16', required: false) vamp_dep = dependency('vamp-sdk', version: '>= 2.9', required: false) +boost_unit_test_dep = dependency('boost', modules: ['unit_test_framework'], version: '>= 1.73', required: false) thread_dep = dependency('threads') have_ladspa = cpp.has_header('ladspa.h', args: extra_include_args) have_lv2 = cpp.has_header('lv2.h', args: extra_include_args) @@ -157,9 +172,9 @@ elif fft == 'kissfft' if fftw3_dep.found() message('(to use FFTW instead, reconfigure with -Dfft=fftw)') endif - feature_sources += ['src/kissfft/kiss_fft.c', 'src/kissfft/kiss_fftr.c'] + feature_sources += ['src/ext/kissfft/kiss_fft.c', 'src/ext/kissfft/kiss_fftr.c'] feature_defines += ['-DHAVE_KISSFFT'] - general_include_dirs += 'src/kissfft' + general_include_dirs += 'src/ext/kissfft' elif fft == 'fftw' if fftw3_dep.found() @@ -204,7 +219,7 @@ if resampler == 'builtin' if samplerate_dep.found() message('(to use libsamplerate instead, reconfigure with -Dresampler=libsamplerate)') endif - library_sources += 'src/dsp/BQResampler.cpp' + library_sources += 'src/common/BQResampler.cpp' feature_defines += ['-DUSE_BQRESAMPLER'] elif resampler == 'libsamplerate' @@ -226,7 +241,7 @@ elif resampler == 'speex' config_summary += { 'Resampler': 'Speex' } message('For resampler: using Speex') message('(consider libsamplerate if time-varying pitch shift is required)') - feature_sources += ['src/speex/resample.c'] + feature_sources += ['src/ext/speex/resample.c'] feature_defines += ['-DUSE_SPEEX'] elif resampler == 'ipp' @@ -313,6 +328,8 @@ if not sndfile_dep.found() endif have_sndfile = sndfile_dep.found() +have_boost_unit_test = boost_unit_test_dep.found() + # General platform and compiler expectations @@ -427,18 +444,22 @@ if cpp.get_id() == 'msvc' endif rubberband_library_name = 'rubberband' rubberband_program_name = 'rubberband-program' + rubberband_program_name_r3 = 'rubberband-program-r3' rubberband_ladspa_name = 'ladspa-rubberband' rubberband_lv2_name = 'lv2-rubberband' rubberband_vamp_name = 'vamp-rubberband' rubberband_jni_name = 'rubberband-jni' + unit_tests_name = 'tests' else rubberband_library_name = 'rubberband' rubberband_dynamic_name = 'rubberband' rubberband_program_name = 'rubberband' + rubberband_program_name_r3 = 'rubberband-r3' rubberband_ladspa_name = 'ladspa-rubberband' rubberband_lv2_name = 'lv2-rubberband' rubberband_vamp_name = 'vamp-rubberband' rubberband_jni_name = 'rubberband-jni' + unit_tests_name = 'tests' endif rubberband_objlib = static_library( @@ -663,8 +684,8 @@ else endif if have_sndfile - target_summary += { 'Command-line utility': [ true, 'Name: ' + rubberband_program_name ] } - message('Will build command-line utility') + message('Will build command-line utilities') + target_summary += { 'Command-line utility (R2)': [ true, 'Name: ' + rubberband_program_name ] } rubberband_program = executable( rubberband_program_name, program_sources, @@ -682,9 +703,66 @@ if have_sndfile ], install: true, ) + target_summary += { 'Command-line utility (R3)': [ true, 'Name: ' + rubberband_program_name_r3 ] } + rubberband_program_r3 = executable( + rubberband_program_name_r3, + program_sources, + include_directories: general_include_dirs, + cpp_args: general_compile_args, + c_args: general_compile_args, + link_args: [ + arch_flags, + feature_libraries, + ], + dependencies: [ + rubberband_objlib_dep, + general_dependencies, + sndfile_dep, + ], + install: true, + ) else - target_summary += { 'Command-line utility': false } - message('Not building command-line utility: libsndfile dependency not found') + message('Not building command-line utilities: libsndfile dependency not found') + target_summary += { 'Command-line utility (R2)': false } + target_summary += { 'Command-line utility (R3)': false } +endif + +if have_boost_unit_test + target_summary += { 'Unit tests': [ true, 'Name: ' + unit_tests_name ] } + message('Will build unit tests: use "meson test -C " to run them') + unit_tests = executable( + unit_tests_name, + unit_test_sources, + cpp_args: general_compile_args, + c_args: general_compile_args, + link_args: [ + arch_flags, + feature_libraries, + ], + dependencies: [ + rubberband_objlib_dep, + general_dependencies, + boost_unit_test_dep, + ], + install: false, + build_by_default: false + ) + general_test_args = [ '--log_level=message' ] + test('Allocators', + unit_tests, args: [ '--run_test=TestAllocators', general_test_args ]) + test('FFT', + unit_tests, args: [ '--run_test=TestFFT', general_test_args ]) + test('Resampler', + unit_tests, args: [ '--run_test=TestResampler', general_test_args ]) + test('VectorOps', + unit_tests, args: [ '--run_test=TestVectorOps', general_test_args ]) + test('VectorOpsComplex', + unit_tests, args: [ '--run_test=TestVectorOpsComplex', general_test_args ]) + test('SignalBits', + unit_tests, args: [ '--run_test=TestSignalBits', general_test_args ]) +else + target_summary += { 'Unit tests': false } + message('Not building unit tests: boost_unit_test_framework dependency not found') endif pkg.generate( diff --git a/otherbuilds/Android.mk b/otherbuilds/Android.mk index 8df6faa..f1ef0b2 100644 --- a/otherbuilds/Android.mk +++ b/otherbuilds/Android.mk @@ -11,52 +11,39 @@ RUBBERBAND_JNI_FILES := \ $(RUBBERBAND_SRC_PATH)/jni/RubberBandStretcherJNI.cpp RUBBERBAND_SRC_FILES := \ - $(RUBBERBAND_SRC_PATH)/base/Profiler.cpp \ - $(RUBBERBAND_SRC_PATH)/system/Thread.cpp \ - $(RUBBERBAND_SRC_PATH)/system/Allocators.cpp \ - $(RUBBERBAND_SRC_PATH)/system/sysutils.cpp \ - $(RUBBERBAND_SRC_PATH)/system/VectorOpsComplex.cpp \ - $(RUBBERBAND_SRC_PATH)/StretcherChannelData.cpp \ - $(RUBBERBAND_SRC_PATH)/dsp/AudioCurveCalculator.cpp \ - $(RUBBERBAND_SRC_PATH)/dsp/FFT.cpp \ - $(RUBBERBAND_SRC_PATH)/dsp/Resampler.cpp \ - $(RUBBERBAND_SRC_PATH)/audiocurves/SilentAudioCurve.cpp \ - $(RUBBERBAND_SRC_PATH)/audiocurves/CompoundAudioCurve.cpp \ - $(RUBBERBAND_SRC_PATH)/audiocurves/HighFrequencyAudioCurve.cpp \ - $(RUBBERBAND_SRC_PATH)/audiocurves/SpectralDifferenceAudioCurve.cpp \ - $(RUBBERBAND_SRC_PATH)/audiocurves/ConstantAudioCurve.cpp \ - $(RUBBERBAND_SRC_PATH)/audiocurves/PercussiveAudioCurve.cpp \ - $(RUBBERBAND_SRC_PATH)/StretcherImpl.cpp \ - $(RUBBERBAND_SRC_PATH)/StretcherProcess.cpp \ - $(RUBBERBAND_SRC_PATH)/StretchCalculator.cpp \ - $(RUBBERBAND_SRC_PATH)/RubberBandStretcher.cpp \ - $(RUBBERBAND_SRC_PATH)/rubberband-c.cpp \ - $(RUBBERBAND_SRC_PATH)/speex/resample.c + $(RUBBERBAND_SRC_PATH)/rubberband-c.cpp \ + $(RUBBERBAND_SRC_PATH)/RubberBandStretcher.cpp \ + $(RUBBERBAND_SRC_PATH)/faster/AudioCurveCalculator.cpp \ + $(RUBBERBAND_SRC_PATH)/faster/CompoundAudioCurve.cpp \ + $(RUBBERBAND_SRC_PATH)/faster/HighFrequencyAudioCurve.cpp \ + $(RUBBERBAND_SRC_PATH)/faster/SilentAudioCurve.cpp \ + $(RUBBERBAND_SRC_PATH)/faster/PercussiveAudioCurve.cpp \ + $(RUBBERBAND_SRC_PATH)/faster/StretcherChannelData.cpp \ + $(RUBBERBAND_SRC_PATH)/faster/StretcherImpl.cpp \ + $(RUBBERBAND_SRC_PATH)/faster/StretcherProcess.cpp \ + $(RUBBERBAND_SRC_PATH)/common/BQResampler.cpp \ + $(RUBBERBAND_SRC_PATH)/common/Profiler.cpp \ + $(RUBBERBAND_SRC_PATH)/common/Resampler.cpp \ + $(RUBBERBAND_SRC_PATH)/common/FFT.cpp \ + $(RUBBERBAND_SRC_PATH)/common/Allocators.cpp \ + $(RUBBERBAND_SRC_PATH)/common/StretchCalculator.cpp \ + $(RUBBERBAND_SRC_PATH)/common/sysutils.cpp \ + $(RUBBERBAND_SRC_PATH)/common/Thread.cpp \ + $(RUBBERBAND_SRC_PATH)/finer/R3StretcherImpl.cpp LOCAL_SRC_FILES += \ $(RUBBERBAND_JNI_FILES) \ $(RUBBERBAND_SRC_FILES) -LOCAL_SRC_FILES += \ - $(RUBBERBAND_SRC_PATH)/kissfft/kiss_fft.c \ - $(RUBBERBAND_SRC_PATH)/kissfft/kiss_fftr.c - LOCAL_CFLAGS_DEBUG := \ -g \ - -mfloat-abi=softfp \ -DWANT_TIMING \ -DFFT_MEASUREMENT LOCAL_CFLAGS_RELEASE := \ -O3 \ - -mfpu=neon \ - -mfloat-abi=softfp \ -ffast-math \ -ftree-vectorize \ - -freciprocal-math \ - -fsingle-precision-constant \ - -D__ARM_ARCH_7__ \ - -DUSE_POMMIER_MATHFUN \ -DNO_TIMING \ -DNO_TIMING_COMPLETE_NOOP @@ -64,9 +51,8 @@ LOCAL_CFLAGS := \ -Wall \ -I$(RUBBERBAND_PATH) \ -I$(RUBBERBAND_SRC_PATH) \ - -DUSE_SPEEX \ - -DUSE_KISSFFT \ - -DPROCESS_SAMPLE_TYPE=float \ + -DUSE_BQRESAMPLER \ + -DUSE_BUILTIN_FFT \ -DLACK_POSIX_MEMALIGN \ -DUSE_OWN_ALIGNED_MALLOC \ -DLACK_SINCOS \ diff --git a/otherbuilds/Makefile.ios b/otherbuilds/Makefile.ios index f6b8abe..d1a1977 100644 --- a/otherbuilds/Makefile.ios +++ b/otherbuilds/Makefile.ios @@ -1,16 +1,16 @@ -CXX := clang++ +CXX := clang++ -stdlib=libc++ -std=c++11 CC := clang -OPTFLAGS := -DNDEBUG -ffast-math -freciprocal-math -O3 -ftree-vectorize +OPTFLAGS := -DNDEBUG -ffast-math -O3 -ftree-vectorize # For the device -ARCHFLAGS_DEV := -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk -miphoneos-version-min=6 -stdlib=libc++ -arch armv7 -arch arm64 -fembed-bitcode +ARCHFLAGS_DEV := -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk -miphoneos-version-min=6 -arch armv7 -arch arm64 -fembed-bitcode # Or for the simulator -ARCHFLAGS_SIM := -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk -miphoneos-version-min=6 -stdlib=libc++ -arch i386 -arch x86_64 -fembed-bitcode +ARCHFLAGS_SIM := -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk -miphoneos-version-min=6 -arch x86_64 -fembed-bitcode -CXXFLAGS_ANY := $(OPTFLAGS) -I. -Isrc -Irubberband -DMALLOC_IS_ALIGNED -DHAVE_VDSP -DUSE_SPEEX -DUSE_POMMIER_MATHFUN -DNO_THREADING -DNO_THREAD_CHECKS -DNO_TIMING -DNO_TIMING_COMPLETE_NOOP -DNDEBUG +CXXFLAGS_ANY := $(OPTFLAGS) -I. -Isrc -Irubberband -DUSE_BQRESAMPLER -DHAVE_VDSP -DNO_THREAD_CHECKS -DUSE_PTHREADS -DNO_TIMING -DMALLOC_IS_ALIGNED -DNDEBUG CXXFLAGS_DEV := $(ARCHFLAGS_DEV) $(CXXFLAGS_ANY) CXXFLAGS_SIM := $(ARCHFLAGS_SIM) $(CXXFLAGS_ANY) @@ -36,59 +36,27 @@ PUBLIC_INCLUDES := \ rubberband/rubberband-c.h \ rubberband/RubberBandStretcher.h -LIBRARY_INCLUDES := \ - src/StretcherChannelData.h \ - src/float_cast/float_cast.h \ - src/StretcherImpl.h \ - src/StretchCalculator.h \ - src/base/Profiler.h \ - src/base/RingBuffer.h \ - src/base/Scavenger.h \ - src/dsp/AudioCurveCalculator.h \ - src/audiocurves/CompoundAudioCurve.h \ - src/audiocurves/ConstantAudioCurve.h \ - src/audiocurves/HighFrequencyAudioCurve.h \ - src/audiocurves/PercussiveAudioCurve.h \ - src/audiocurves/SilentAudioCurve.h \ - src/audiocurves/SpectralDifferenceAudioCurve.h \ - src/dsp/Resampler.h \ - src/dsp/FFT.h \ - src/dsp/MovingMedian.h \ - src/dsp/SincWindow.h \ - src/dsp/Window.h \ - src/system/Allocators.h \ - src/system/Thread.h \ - src/system/VectorOps.h \ - src/system/VectorOpsComplex.h \ - src/system/sysutils.h - LIBRARY_SOURCES := \ src/rubberband-c.cpp \ src/RubberBandStretcher.cpp \ - src/StretcherProcess.cpp \ - src/StretchCalculator.cpp \ - src/base/Profiler.cpp \ - src/dsp/AudioCurveCalculator.cpp \ - src/audiocurves/CompoundAudioCurve.cpp \ - src/audiocurves/SpectralDifferenceAudioCurve.cpp \ - src/audiocurves/HighFrequencyAudioCurve.cpp \ - src/audiocurves/SilentAudioCurve.cpp \ - src/audiocurves/ConstantAudioCurve.cpp \ - src/audiocurves/PercussiveAudioCurve.cpp \ - src/dsp/Resampler.cpp \ - src/dsp/FFT.cpp \ - src/system/Allocators.cpp \ - src/system/sysutils.cpp \ - src/system/Thread.cpp \ - src/system/VectorOpsComplex.cpp \ - src/StretcherChannelData.cpp \ - src/StretcherImpl.cpp - -# For Speex resampler -- comment these lines out if not specifying USE_SPEEX -LIBRARY_INCLUDES := $(LIBRARY_INCLUDES) \ - src/speex/speex_resampler.h -LIBRARY_SOURCES := $(LIBRARY_SOURCES) \ - src/speex/resample.c + src/faster/AudioCurveCalculator.cpp \ + src/faster/CompoundAudioCurve.cpp \ + src/faster/HighFrequencyAudioCurve.cpp \ + src/faster/SilentAudioCurve.cpp \ + src/faster/PercussiveAudioCurve.cpp \ + src/faster/R2Stretcher.cpp \ + src/faster/StretcherChannelData.cpp \ + src/faster/StretcherProcess.cpp \ + src/common/Allocators.cpp \ + src/common/BQResampler.cpp \ + src/common/FFT.cpp \ + src/common/Log.cpp \ + src/common/Profiler.cpp \ + src/common/Resampler.cpp \ + src/common/StretchCalculator.cpp \ + src/common/sysutils.cpp \ + src/common/Thread.cpp \ + src/finer/R3Stretcher.cpp LIBRARY_OBJECTS_DEV := $(LIBRARY_SOURCES:.cpp=.dev.o) LIBRARY_OBJECTS_DEV := $(LIBRARY_OBJECTS_DEV:.c=.dev.o) diff --git a/otherbuilds/Makefile.linux b/otherbuilds/Makefile.linux index ec361bf..e12cbcd 100644 --- a/otherbuilds/Makefile.linux +++ b/otherbuilds/Makefile.linux @@ -1,12 +1,12 @@ -CXX := g++ +CXX := g++ -std=c++11 CC := gcc OPTFLAGS := -DNDEBUG -ffast-math -O3 -ftree-vectorize ARCHFLAGS := -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 +CXXFLAGS := $(ARCHFLAGS) $(OPTFLAGS) -I. -Isrc -Irubberband -DUSE_BQRESAMPLER -DUSE_BUILTIN_FFT -DNO_THREAD_CHECKS -DUSE_PTHREADS -DNO_TIMING -DHAVE_POSIX_MEMALIGN -DNDEBUG CFLAGS := $(ARCHFLAGS) $(OPTFLAGS) @@ -25,51 +25,27 @@ PUBLIC_INCLUDES := \ rubberband/rubberband-c.h \ rubberband/RubberBandStretcher.h -LIBRARY_INCLUDES := \ - src/StretcherChannelData.h \ - src/float_cast/float_cast.h \ - src/StretcherImpl.h \ - src/StretchCalculator.h \ - src/base/Profiler.h \ - src/base/RingBuffer.h \ - src/base/Scavenger.h \ - src/dsp/AudioCurveCalculator.h \ - src/audiocurves/CompoundAudioCurve.h \ - src/audiocurves/ConstantAudioCurve.h \ - src/audiocurves/HighFrequencyAudioCurve.h \ - src/audiocurves/PercussiveAudioCurve.h \ - src/audiocurves/SilentAudioCurve.h \ - src/audiocurves/SpectralDifferenceAudioCurve.h \ - src/dsp/Resampler.h \ - src/dsp/FFT.h \ - src/dsp/MovingMedian.h \ - src/dsp/SincWindow.h \ - src/dsp/Window.h \ - src/system/Allocators.h \ - src/system/Thread.h \ - src/system/VectorOps.h \ - src/system/sysutils.h - LIBRARY_SOURCES := \ src/rubberband-c.cpp \ src/RubberBandStretcher.cpp \ - src/StretcherProcess.cpp \ - src/StretchCalculator.cpp \ - src/base/Profiler.cpp \ - src/dsp/AudioCurveCalculator.cpp \ - src/audiocurves/CompoundAudioCurve.cpp \ - src/audiocurves/SpectralDifferenceAudioCurve.cpp \ - src/audiocurves/HighFrequencyAudioCurve.cpp \ - src/audiocurves/SilentAudioCurve.cpp \ - src/audiocurves/ConstantAudioCurve.cpp \ - src/audiocurves/PercussiveAudioCurve.cpp \ - src/dsp/Resampler.cpp \ - src/dsp/FFT.cpp \ - src/system/Allocators.cpp \ - src/system/sysutils.cpp \ - src/system/Thread.cpp \ - src/StretcherChannelData.cpp \ - src/StretcherImpl.cpp + src/faster/AudioCurveCalculator.cpp \ + src/faster/CompoundAudioCurve.cpp \ + src/faster/HighFrequencyAudioCurve.cpp \ + src/faster/SilentAudioCurve.cpp \ + src/faster/PercussiveAudioCurve.cpp \ + src/faster/R2Stretcher.cpp \ + src/faster/StretcherChannelData.cpp \ + src/faster/StretcherProcess.cpp \ + src/common/Allocators.cpp \ + src/common/BQResampler.cpp \ + src/common/FFT.cpp \ + src/common/Log.cpp \ + src/common/Profiler.cpp \ + src/common/Resampler.cpp \ + src/common/StretchCalculator.cpp \ + src/common/sysutils.cpp \ + src/common/Thread.cpp \ + src/finer/R3Stretcher.cpp LIBRARY_OBJECTS := $(LIBRARY_SOURCES:.cpp=.o) LIBRARY_OBJECTS := $(LIBRARY_OBJECTS:.c=.o) @@ -94,88 +70,138 @@ depend: src/rubberband-c.o: rubberband/rubberband-c.h src/rubberband-c.o: rubberband/RubberBandStretcher.h -src/RubberBandStretcher.o: src/StretcherImpl.h -src/RubberBandStretcher.o: rubberband/RubberBandStretcher.h src/dsp/Window.h -src/RubberBandStretcher.o: src/dsp/SincWindow.h src/dsp/FFT.h -src/RubberBandStretcher.o: src/audiocurves/CompoundAudioCurve.h -src/RubberBandStretcher.o: src/dsp/AudioCurveCalculator.h -src/RubberBandStretcher.o: src/audiocurves/PercussiveAudioCurve.h -src/RubberBandStretcher.o: src/audiocurves/HighFrequencyAudioCurve.h -src/RubberBandStretcher.o: src/dsp/SampleFilter.h src/base/RingBuffer.h -src/RubberBandStretcher.o: src/base/Scavenger.h src/system/Thread.h -src/RubberBandStretcher.o: src/system/sysutils.h -src/StretcherProcess.o: src/StretcherImpl.h rubberband/RubberBandStretcher.h -src/StretcherProcess.o: src/dsp/Window.h src/dsp/SincWindow.h src/dsp/FFT.h -src/StretcherProcess.o: src/audiocurves/CompoundAudioCurve.h -src/StretcherProcess.o: src/dsp/AudioCurveCalculator.h -src/StretcherProcess.o: src/audiocurves/PercussiveAudioCurve.h -src/StretcherProcess.o: src/audiocurves/HighFrequencyAudioCurve.h -src/StretcherProcess.o: src/dsp/SampleFilter.h src/base/RingBuffer.h -src/StretcherProcess.o: src/base/Scavenger.h src/system/Thread.h -src/StretcherProcess.o: src/system/sysutils.h src/audiocurves/PercussiveAudioCurve.h -src/StretcherProcess.o: src/audiocurves/HighFrequencyAudioCurve.h -src/StretcherProcess.o: src/audiocurves/ConstantAudioCurve.h src/StretchCalculator.h -src/StretcherProcess.o: src/StretcherChannelData.h src/dsp/Resampler.h -src/StretcherProcess.o: src/base/Profiler.h src/system/VectorOps.h -src/StretcherProcess.o: src/system/sysutils.h -src/StretchCalculator.o: src/StretchCalculator.h src/system/sysutils.h -src/base/Profiler.o: src/base/Profiler.h src/system/sysutils.h -src/dsp/AudioCurveCalculator.o: src/dsp/AudioCurveCalculator.h -src/audiocurves/CompoundAudioCurve.o: src/audiocurves/CompoundAudioCurve.h -src/audiocurves/CompoundAudioCurve.o: src/dsp/AudioCurveCalculator.h -src/audiocurves/CompoundAudioCurve.o: src/audiocurves/PercussiveAudioCurve.h -src/audiocurves/CompoundAudioCurve.o: src/audiocurves/HighFrequencyAudioCurve.h -src/audiocurves/CompoundAudioCurve.o: src/dsp/SampleFilter.h src/dsp/MovingMedian.h -src/audiocurves/SpectralDifferenceAudioCurve.o: src/audiocurves/SpectralDifferenceAudioCurve.h -src/audiocurves/SpectralDifferenceAudioCurve.o: src/dsp/AudioCurveCalculator.h -src/audiocurves/SpectralDifferenceAudioCurve.o: src/dsp/Window.h -src/audiocurves/SpectralDifferenceAudioCurve.o: src/system/sysutils.h -src/audiocurves/SpectralDifferenceAudioCurve.o: src/system/VectorOps.h -src/audiocurves/SpectralDifferenceAudioCurve.o: src/system/sysutils.h -src/audiocurves/HighFrequencyAudioCurve.o: src/audiocurves/HighFrequencyAudioCurve.h -src/audiocurves/HighFrequencyAudioCurve.o: src/dsp/AudioCurveCalculator.h -src/audiocurves/SilentAudioCurve.o: src/audiocurves/SilentAudioCurve.h -src/audiocurves/SilentAudioCurve.o: src/dsp/AudioCurveCalculator.h -src/audiocurves/ConstantAudioCurve.o: src/audiocurves/ConstantAudioCurve.h -src/audiocurves/ConstantAudioCurve.o: src/dsp/AudioCurveCalculator.h -src/audiocurves/PercussiveAudioCurve.o: src/audiocurves/PercussiveAudioCurve.h -src/audiocurves/PercussiveAudioCurve.o: src/dsp/AudioCurveCalculator.h -src/audiocurves/PercussiveAudioCurve.o: src/system/VectorOps.h src/system/sysutils.h -src/dsp/Resampler.o: src/dsp/Resampler.h src/system/sysutils.h -src/dsp/Resampler.o: src/base/Profiler.h -src/dsp/FFT.o: src/dsp/FFT.h src/system/sysutils.h src/system/Thread.h -src/dsp/FFT.o: src/base/Profiler.h src/system/VectorOps.h -src/dsp/FFT.o: src/system/sysutils.h -src/system/Allocators.o: src/system/Allocators.h src/system/VectorOps.h -src/system/Allocators.o: src/system/sysutils.h -src/system/sysutils.o: src/system/sysutils.h -src/system/Thread.o: src/system/Thread.h -src/StretcherChannelData.o: src/StretcherChannelData.h src/StretcherImpl.h -src/StretcherChannelData.o: rubberband/RubberBandStretcher.h src/dsp/Window.h -src/StretcherChannelData.o: src/dsp/SincWindow.h src/dsp/FFT.h -src/StretcherChannelData.o: src/audiocurves/CompoundAudioCurve.h -src/StretcherChannelData.o: src/dsp/AudioCurveCalculator.h -src/StretcherChannelData.o: src/audiocurves/PercussiveAudioCurve.h -src/StretcherChannelData.o: src/audiocurves/HighFrequencyAudioCurve.h -src/StretcherChannelData.o: src/dsp/SampleFilter.h src/base/RingBuffer.h -src/StretcherChannelData.o: src/base/Scavenger.h src/system/Thread.h -src/StretcherChannelData.o: src/system/sysutils.h src/dsp/Resampler.h -src/StretcherChannelData.o: src/system/Allocators.h src/system/VectorOps.h -src/StretcherChannelData.o: src/system/sysutils.h -src/StretcherImpl.o: src/StretcherImpl.h rubberband/RubberBandStretcher.h -src/StretcherImpl.o: src/dsp/Window.h src/dsp/SincWindow.h src/dsp/FFT.h -src/StretcherImpl.o: src/audiocurves/CompoundAudioCurve.h -src/StretcherImpl.o: src/dsp/AudioCurveCalculator.h -src/StretcherImpl.o: src/audiocurves/PercussiveAudioCurve.h -src/StretcherImpl.o: src/audiocurves/HighFrequencyAudioCurve.h src/dsp/SampleFilter.h -src/StretcherImpl.o: src/base/RingBuffer.h src/base/Scavenger.h -src/StretcherImpl.o: src/system/Thread.h src/system/sysutils.h -src/StretcherImpl.o: src/audiocurves/PercussiveAudioCurve.h -src/StretcherImpl.o: src/audiocurves/HighFrequencyAudioCurve.h -src/StretcherImpl.o: src/audiocurves/SpectralDifferenceAudioCurve.h src/dsp/Window.h -src/StretcherImpl.o: src/system/VectorOps.h src/system/sysutils.h -src/StretcherImpl.o: src/audiocurves/SilentAudioCurve.h src/audiocurves/ConstantAudioCurve.h -src/StretcherImpl.o: src/dsp/Resampler.h src/StretchCalculator.h -src/StretcherImpl.o: src/StretcherChannelData.h src/base/Profiler.h -main/main.o: rubberband/RubberBandStretcher.h src/system/sysutils.h -main/main.o: src/base/Profiler.h +src/RubberBandStretcher.o: src/faster/R2Stretcher.h +src/RubberBandStretcher.o: rubberband/RubberBandStretcher.h +src/RubberBandStretcher.o: src/common/Window.h src/common/sysutils.h +src/RubberBandStretcher.o: src/common/VectorOps.h src/common/Allocators.h +src/RubberBandStretcher.o: src/common/FFT.h src/common/RingBuffer.h +src/RubberBandStretcher.o: src/common/Scavenger.h src/common/Thread.h +src/RubberBandStretcher.o: src/common/Thread.h src/common/Log.h +src/RubberBandStretcher.o: src/common/sysutils.h src/faster/SincWindow.h +src/RubberBandStretcher.o: src/common/VectorOps.h src/common/Allocators.h +src/RubberBandStretcher.o: src/faster/CompoundAudioCurve.h +src/RubberBandStretcher.o: src/faster/PercussiveAudioCurve.h +src/RubberBandStretcher.o: src/faster/AudioCurveCalculator.h +src/RubberBandStretcher.o: src/faster/HighFrequencyAudioCurve.h +src/RubberBandStretcher.o: src/common/SampleFilter.h src/finer/R3Stretcher.h +src/RubberBandStretcher.o: src/finer/BinSegmenter.h src/finer/BinClassifier.h +src/RubberBandStretcher.o: src/common/MovingMedian.h +src/RubberBandStretcher.o: src/common/SampleFilter.h src/common/FixedVector.h +src/RubberBandStretcher.o: src/common/SingleThreadRingBuffer.h +src/RubberBandStretcher.o: src/common/HistogramFilter.h src/common/mathmisc.h +src/RubberBandStretcher.o: src/finer/Guide.h src/finer/Peak.h +src/RubberBandStretcher.o: src/finer/PhaseAdvance.h +src/RubberBandStretcher.o: src/common/StretchCalculator.h src/common/Log.h +src/RubberBandStretcher.o: src/common/Resampler.h src/common/FixedVector.h +src/RubberBandStretcher.o: src/common/VectorOpsComplex.h +src/faster/AudioCurveCalculator.o: src/faster/AudioCurveCalculator.h +src/faster/AudioCurveCalculator.o: src/common/sysutils.h +src/faster/CompoundAudioCurve.o: src/faster/CompoundAudioCurve.h +src/faster/CompoundAudioCurve.o: src/faster/PercussiveAudioCurve.h +src/faster/CompoundAudioCurve.o: src/faster/AudioCurveCalculator.h +src/faster/CompoundAudioCurve.o: src/common/sysutils.h +src/faster/CompoundAudioCurve.o: src/faster/HighFrequencyAudioCurve.h +src/faster/CompoundAudioCurve.o: src/common/SampleFilter.h +src/faster/CompoundAudioCurve.o: src/common/MovingMedian.h +src/faster/CompoundAudioCurve.o: src/common/SampleFilter.h +src/faster/CompoundAudioCurve.o: src/common/FixedVector.h +src/faster/CompoundAudioCurve.o: src/common/Allocators.h +src/faster/CompoundAudioCurve.o: src/common/VectorOps.h src/common/sysutils.h +src/faster/CompoundAudioCurve.o: src/common/SingleThreadRingBuffer.h +src/faster/HighFrequencyAudioCurve.o: src/faster/HighFrequencyAudioCurve.h +src/faster/HighFrequencyAudioCurve.o: src/faster/AudioCurveCalculator.h +src/faster/HighFrequencyAudioCurve.o: src/common/sysutils.h +src/faster/SilentAudioCurve.o: src/faster/SilentAudioCurve.h +src/faster/SilentAudioCurve.o: src/faster/AudioCurveCalculator.h +src/faster/SilentAudioCurve.o: src/common/sysutils.h +src/faster/PercussiveAudioCurve.o: src/faster/PercussiveAudioCurve.h +src/faster/PercussiveAudioCurve.o: src/faster/AudioCurveCalculator.h +src/faster/PercussiveAudioCurve.o: src/common/sysutils.h +src/faster/PercussiveAudioCurve.o: src/common/Allocators.h +src/faster/PercussiveAudioCurve.o: src/common/VectorOps.h +src/faster/R2Stretcher.o: src/faster/R2Stretcher.h +src/faster/R2Stretcher.o: rubberband/RubberBandStretcher.h +src/faster/R2Stretcher.o: src/common/Window.h src/common/sysutils.h +src/faster/R2Stretcher.o: src/common/VectorOps.h src/common/Allocators.h +src/faster/R2Stretcher.o: src/common/FFT.h src/common/RingBuffer.h +src/faster/R2Stretcher.o: src/common/Scavenger.h src/common/Thread.h +src/faster/R2Stretcher.o: src/common/Thread.h src/common/Log.h +src/faster/R2Stretcher.o: src/common/sysutils.h src/faster/SincWindow.h +src/faster/R2Stretcher.o: src/common/VectorOps.h src/common/Allocators.h +src/faster/R2Stretcher.o: src/faster/CompoundAudioCurve.h +src/faster/R2Stretcher.o: src/faster/PercussiveAudioCurve.h +src/faster/R2Stretcher.o: src/faster/AudioCurveCalculator.h +src/faster/R2Stretcher.o: src/faster/HighFrequencyAudioCurve.h +src/faster/R2Stretcher.o: src/common/SampleFilter.h +src/faster/R2Stretcher.o: src/faster/SilentAudioCurve.h +src/faster/R2Stretcher.o: src/faster/StretcherChannelData.h +src/faster/R2Stretcher.o: src/common/StretchCalculator.h src/common/Log.h +src/faster/R2Stretcher.o: src/common/Resampler.h src/common/Profiler.h +src/faster/StretcherChannelData.o: src/faster/StretcherChannelData.h +src/faster/StretcherChannelData.o: src/faster/R2Stretcher.h +src/faster/StretcherChannelData.o: rubberband/RubberBandStretcher.h +src/faster/StretcherChannelData.o: src/common/Window.h src/common/sysutils.h +src/faster/StretcherChannelData.o: src/common/VectorOps.h +src/faster/StretcherChannelData.o: src/common/Allocators.h src/common/FFT.h +src/faster/StretcherChannelData.o: src/common/RingBuffer.h +src/faster/StretcherChannelData.o: src/common/Scavenger.h src/common/Thread.h +src/faster/StretcherChannelData.o: src/common/Thread.h src/common/Log.h +src/faster/StretcherChannelData.o: src/common/sysutils.h +src/faster/StretcherChannelData.o: src/faster/SincWindow.h +src/faster/StretcherChannelData.o: src/common/VectorOps.h +src/faster/StretcherChannelData.o: src/common/Allocators.h +src/faster/StretcherChannelData.o: src/faster/CompoundAudioCurve.h +src/faster/StretcherChannelData.o: src/faster/PercussiveAudioCurve.h +src/faster/StretcherChannelData.o: src/faster/AudioCurveCalculator.h +src/faster/StretcherChannelData.o: src/faster/HighFrequencyAudioCurve.h +src/faster/StretcherChannelData.o: src/common/SampleFilter.h +src/faster/StretcherChannelData.o: src/common/Resampler.h +src/faster/StretcherProcess.o: src/faster/R2Stretcher.h +src/faster/StretcherProcess.o: rubberband/RubberBandStretcher.h +src/faster/StretcherProcess.o: src/common/Window.h src/common/sysutils.h +src/faster/StretcherProcess.o: src/common/VectorOps.h src/common/Allocators.h +src/faster/StretcherProcess.o: src/common/FFT.h src/common/RingBuffer.h +src/faster/StretcherProcess.o: src/common/Scavenger.h src/common/Thread.h +src/faster/StretcherProcess.o: src/common/Thread.h src/common/Log.h +src/faster/StretcherProcess.o: src/common/sysutils.h src/faster/SincWindow.h +src/faster/StretcherProcess.o: src/common/VectorOps.h src/common/Allocators.h +src/faster/StretcherProcess.o: src/faster/CompoundAudioCurve.h +src/faster/StretcherProcess.o: src/faster/PercussiveAudioCurve.h +src/faster/StretcherProcess.o: src/faster/AudioCurveCalculator.h +src/faster/StretcherProcess.o: src/faster/HighFrequencyAudioCurve.h +src/faster/StretcherProcess.o: src/common/SampleFilter.h +src/faster/StretcherProcess.o: src/faster/StretcherChannelData.h +src/faster/StretcherProcess.o: src/common/StretchCalculator.h +src/faster/StretcherProcess.o: src/common/Log.h src/common/Resampler.h +src/faster/StretcherProcess.o: src/common/Profiler.h src/common/mathmisc.h +src/common/Allocators.o: src/common/Allocators.h src/common/VectorOps.h +src/common/Allocators.o: src/common/sysutils.h +src/common/BQResampler.o: src/common/BQResampler.h src/common/Allocators.h +src/common/BQResampler.o: src/common/VectorOps.h src/common/sysutils.h +src/common/FFT.o: src/common/FFT.h src/common/sysutils.h src/common/Thread.h +src/common/FFT.o: src/common/Profiler.h src/common/Allocators.h +src/common/FFT.o: src/common/VectorOps.h src/common/VectorOpsComplex.h +src/common/Log.o: src/common/Log.h +src/common/Profiler.o: src/common/Profiler.h src/common/sysutils.h +src/common/Profiler.o: src/common/Thread.h +src/common/Resampler.o: src/common/Resampler.h src/common/sysutils.h +src/common/Resampler.o: src/common/Allocators.h src/common/VectorOps.h +src/common/StretchCalculator.o: src/common/StretchCalculator.h +src/common/StretchCalculator.o: src/common/Log.h src/common/sysutils.h +src/common/sysutils.o: src/common/sysutils.h +src/common/Thread.o: src/common/Thread.h +src/finer/R3Stretcher.o: src/finer/R3Stretcher.h src/finer/BinSegmenter.h +src/finer/R3Stretcher.o: src/finer/BinClassifier.h src/common/Allocators.h +src/finer/R3Stretcher.o: src/common/MovingMedian.h src/common/SampleFilter.h +src/finer/R3Stretcher.o: src/common/FixedVector.h src/common/Allocators.h +src/finer/R3Stretcher.o: src/common/VectorOps.h src/common/sysutils.h +src/finer/R3Stretcher.o: src/common/SingleThreadRingBuffer.h +src/finer/R3Stretcher.o: src/common/RingBuffer.h src/common/HistogramFilter.h +src/finer/R3Stretcher.o: src/common/mathmisc.h src/finer/Guide.h +src/finer/R3Stretcher.o: src/common/Log.h src/finer/Peak.h +src/finer/R3Stretcher.o: src/finer/PhaseAdvance.h +src/finer/R3Stretcher.o: src/common/StretchCalculator.h src/common/Log.h +src/finer/R3Stretcher.o: src/common/Resampler.h src/common/FFT.h +src/finer/R3Stretcher.o: src/common/FixedVector.h src/common/Window.h +src/finer/R3Stretcher.o: src/common/VectorOpsComplex.h +src/finer/R3Stretcher.o: rubberband/RubberBandStretcher.h diff --git a/otherbuilds/Makefile.macos b/otherbuilds/Makefile.macos index eae185f..5819dbd 100644 --- a/otherbuilds/Makefile.macos +++ b/otherbuilds/Makefile.macos @@ -1,12 +1,12 @@ -CXX := clang++ -stdlib=libc++ +CXX := clang++ -stdlib=libc++ -std=c++11 CC := clang OPTFLAGS := -DNDEBUG -ffast-math -O3 -ftree-vectorize ARCHFLAGS := -mmacosx-version-min=10.7 -CXXFLAGS := $(ARCHFLAGS) $(OPTFLAGS) -I. -Isrc -Irubberband -I/usr/local/include -DUSE_PTHREADS -DMALLOC_IS_ALIGNED -DHAVE_VDSP -DUSE_SPEEX -DNO_THREAD_CHECKS -DNO_TIMING +CXXFLAGS := $(ARCHFLAGS) $(OPTFLAGS) -I. -Isrc -Irubberband -DUSE_BQRESAMPLER -DHAVE_VDSP -DNO_THREAD_CHECKS -DUSE_PTHREADS -DNO_TIMING -DMALLOC_IS_ALIGNED -DNDEBUG CFLAGS := $(ARCHFLAGS) $(OPTFLAGS) @@ -25,59 +25,27 @@ PUBLIC_INCLUDES := \ rubberband/rubberband-c.h \ rubberband/RubberBandStretcher.h -LIBRARY_INCLUDES := \ - src/StretcherChannelData.h \ - src/float_cast/float_cast.h \ - src/StretcherImpl.h \ - src/StretchCalculator.h \ - src/base/Profiler.h \ - src/base/RingBuffer.h \ - src/base/Scavenger.h \ - src/dsp/AudioCurveCalculator.h \ - src/audiocurves/CompoundAudioCurve.h \ - src/audiocurves/ConstantAudioCurve.h \ - src/audiocurves/HighFrequencyAudioCurve.h \ - src/audiocurves/PercussiveAudioCurve.h \ - src/audiocurves/SilentAudioCurve.h \ - src/audiocurves/SpectralDifferenceAudioCurve.h \ - src/dsp/Resampler.h \ - src/dsp/FFT.h \ - src/dsp/MovingMedian.h \ - src/dsp/SincWindow.h \ - src/dsp/Window.h \ - src/system/Allocators.h \ - src/system/Thread.h \ - src/system/VectorOps.h \ - src/system/VectorOpsComplex.h \ - src/system/sysutils.h - LIBRARY_SOURCES := \ src/rubberband-c.cpp \ src/RubberBandStretcher.cpp \ - src/StretcherProcess.cpp \ - src/StretchCalculator.cpp \ - src/base/Profiler.cpp \ - src/dsp/AudioCurveCalculator.cpp \ - src/audiocurves/CompoundAudioCurve.cpp \ - src/audiocurves/SpectralDifferenceAudioCurve.cpp \ - src/audiocurves/HighFrequencyAudioCurve.cpp \ - src/audiocurves/SilentAudioCurve.cpp \ - src/audiocurves/ConstantAudioCurve.cpp \ - src/audiocurves/PercussiveAudioCurve.cpp \ - src/dsp/Resampler.cpp \ - src/dsp/FFT.cpp \ - src/system/Allocators.cpp \ - src/system/sysutils.cpp \ - src/system/Thread.cpp \ - src/system/VectorOpsComplex.cpp \ - src/StretcherChannelData.cpp \ - src/StretcherImpl.cpp - -# For Speex resampler -- comment these lines out if not specifying USE_SPEEX -LIBRARY_INCLUDES := $(LIBRARY_INCLUDES) \ - src/speex/speex_resampler.h -LIBRARY_SOURCES := $(LIBRARY_SOURCES) \ - src/speex/resample.c + src/faster/AudioCurveCalculator.cpp \ + src/faster/CompoundAudioCurve.cpp \ + src/faster/HighFrequencyAudioCurve.cpp \ + src/faster/SilentAudioCurve.cpp \ + src/faster/PercussiveAudioCurve.cpp \ + src/faster/R2Stretcher.cpp \ + src/faster/StretcherChannelData.cpp \ + src/faster/StretcherProcess.cpp \ + src/common/Allocators.cpp \ + src/common/BQResampler.cpp \ + src/common/FFT.cpp \ + src/common/Log.cpp \ + src/common/Profiler.cpp \ + src/common/Resampler.cpp \ + src/common/StretchCalculator.cpp \ + src/common/sysutils.cpp \ + src/common/Thread.cpp \ + src/finer/R3Stretcher.cpp LIBRARY_OBJECTS := $(LIBRARY_SOURCES:.cpp=.o) LIBRARY_OBJECTS := $(LIBRARY_OBJECTS:.c=.o) @@ -103,71 +71,138 @@ depend: src/rubberband-c.o: rubberband/rubberband-c.h src/rubberband-c.o: rubberband/RubberBandStretcher.h -src/RubberBandStretcher.o: src/StretcherImpl.h -src/RubberBandStretcher.o: rubberband/RubberBandStretcher.h src/dsp/Window.h -src/RubberBandStretcher.o: src/dsp/FFT.h src/base/RingBuffer.h -src/RubberBandStretcher.o: src/base/Scavenger.h src/system/Thread.h -src/RubberBandStretcher.o: src/system/Thread.h src/system/sysutils.h -src/StretcherProcess.o: src/StretcherImpl.h rubberband/RubberBandStretcher.h -src/StretcherProcess.o: src/dsp/Window.h src/dsp/FFT.h src/base/RingBuffer.h -src/StretcherProcess.o: src/base/Scavenger.h src/system/Thread.h -src/StretcherProcess.o: src/system/Thread.h src/system/sysutils.h -src/StretcherProcess.o: src/audiocurves/PercussiveAudioCurve.h -src/StretcherProcess.o: src/dsp/AudioCurveCalculator.h -src/StretcherProcess.o: src/audiocurves/HighFrequencyAudioCurve.h -src/StretcherProcess.o: src/audiocurves/ConstantAudioCurve.h src/StretchCalculator.h -src/StretcherProcess.o: src/StretcherChannelData.h src/dsp/Resampler.h -src/StretcherProcess.o: src/base/Profiler.h src/system/VectorOps.h -src/StretcherProcess.o: src/system/sysutils.h -src/StretchCalculator.o: src/StretchCalculator.h src/system/sysutils.h -src/system/Thread.o: src/system/Thread.h -src/base/Profiler.o: src/base/Profiler.h src/system/sysutils.h -src/dsp/AudioCurveCalculator.o: src/dsp/AudioCurveCalculator.h -src/dsp/AudioCurveCalculator.o: src/system/sysutils.h -src/audiocurves/SpectralDifferenceAudioCurve.o: src/audiocurves/SpectralDifferenceAudioCurve.h -src/audiocurves/SpectralDifferenceAudioCurve.o: src/dsp/AudioCurveCalculator.h -src/audiocurves/SpectralDifferenceAudioCurve.o: src/system/sysutils.h -src/audiocurves/SpectralDifferenceAudioCurve.o: src/dsp/Window.h -src/audiocurves/SpectralDifferenceAudioCurve.o: src/system/VectorOps.h -src/audiocurves/SpectralDifferenceAudioCurve.o: src/system/sysutils.h -src/audiocurves/HighFrequencyAudioCurve.o: src/audiocurves/HighFrequencyAudioCurve.h -src/audiocurves/HighFrequencyAudioCurve.o: src/dsp/AudioCurveCalculator.h -src/audiocurves/HighFrequencyAudioCurve.o: src/system/sysutils.h -src/audiocurves/SilentAudioCurve.o: src/audiocurves/SilentAudioCurve.h -src/audiocurves/SilentAudioCurve.o: src/dsp/AudioCurveCalculator.h -src/audiocurves/SilentAudioCurve.o: src/system/sysutils.h -src/audiocurves/ConstantAudioCurve.o: src/audiocurves/ConstantAudioCurve.h -src/audiocurves/ConstantAudioCurve.o: src/dsp/AudioCurveCalculator.h -src/audiocurves/ConstantAudioCurve.o: src/system/sysutils.h -src/audiocurves/PercussiveAudioCurve.o: src/audiocurves/PercussiveAudioCurve.h -src/audiocurves/PercussiveAudioCurve.o: src/dsp/AudioCurveCalculator.h -src/audiocurves/PercussiveAudioCurve.o: src/system/sysutils.h src/system/VectorOps.h -src/audiocurves/PercussiveAudioCurve.o: src/system/sysutils.h -src/dsp/Resampler.o: src/dsp/Resampler.h src/system/sysutils.h -src/dsp/Resampler.o: src/base/Profiler.h -src/dsp/FFT.o: src/dsp/FFT.h src/system/sysutils.h src/system/Thread.h -src/dsp/FFT.o: src/base/Profiler.h src/system/VectorOps.h -src/dsp/FFT.o: src/system/sysutils.h -src/system/Allocators.o: src/system/Allocators.h src/system/VectorOps.h -src/system/Allocators.o: src/system/sysutils.h -src/system/sysutils.o: src/system/sysutils.h -src/StretcherChannelData.o: src/StretcherChannelData.h src/StretcherImpl.h -src/StretcherChannelData.o: rubberband/RubberBandStretcher.h src/dsp/Window.h -src/StretcherChannelData.o: src/dsp/FFT.h src/base/RingBuffer.h -src/StretcherChannelData.o: src/base/Scavenger.h src/system/Thread.h -src/StretcherChannelData.o: src/system/Thread.h src/system/sysutils.h -src/StretcherChannelData.o: src/dsp/Resampler.h src/system/Allocators.h -src/StretcherChannelData.o: src/system/VectorOps.h src/system/sysutils.h -src/StretcherImpl.o: src/StretcherImpl.h rubberband/RubberBandStretcher.h -src/StretcherImpl.o: src/dsp/Window.h src/dsp/FFT.h src/base/RingBuffer.h -src/StretcherImpl.o: src/base/Scavenger.h src/system/Thread.h src/system/Thread.h -src/StretcherImpl.o: src/system/sysutils.h src/audiocurves/PercussiveAudioCurve.h -src/StretcherImpl.o: src/dsp/AudioCurveCalculator.h -src/StretcherImpl.o: src/audiocurves/HighFrequencyAudioCurve.h -src/StretcherImpl.o: src/audiocurves/SpectralDifferenceAudioCurve.h src/dsp/Window.h -src/StretcherImpl.o: src/system/VectorOps.h src/system/sysutils.h -src/StretcherImpl.o: src/audiocurves/SilentAudioCurve.h src/audiocurves/ConstantAudioCurve.h -src/StretcherImpl.o: src/dsp/Resampler.h src/StretchCalculator.h -src/StretcherImpl.o: src/StretcherChannelData.h src/base/Profiler.h -main/main.o: rubberband/RubberBandStretcher.h src/system/sysutils.h -main/main.o: src/base/Profiler.h +src/RubberBandStretcher.o: src/faster/R2Stretcher.h +src/RubberBandStretcher.o: rubberband/RubberBandStretcher.h +src/RubberBandStretcher.o: src/common/Window.h src/common/sysutils.h +src/RubberBandStretcher.o: src/common/VectorOps.h src/common/Allocators.h +src/RubberBandStretcher.o: src/common/FFT.h src/common/RingBuffer.h +src/RubberBandStretcher.o: src/common/Scavenger.h src/common/Thread.h +src/RubberBandStretcher.o: src/common/Thread.h src/common/Log.h +src/RubberBandStretcher.o: src/common/sysutils.h src/faster/SincWindow.h +src/RubberBandStretcher.o: src/common/VectorOps.h src/common/Allocators.h +src/RubberBandStretcher.o: src/faster/CompoundAudioCurve.h +src/RubberBandStretcher.o: src/faster/PercussiveAudioCurve.h +src/RubberBandStretcher.o: src/faster/AudioCurveCalculator.h +src/RubberBandStretcher.o: src/faster/HighFrequencyAudioCurve.h +src/RubberBandStretcher.o: src/common/SampleFilter.h src/finer/R3Stretcher.h +src/RubberBandStretcher.o: src/finer/BinSegmenter.h src/finer/BinClassifier.h +src/RubberBandStretcher.o: src/common/MovingMedian.h +src/RubberBandStretcher.o: src/common/SampleFilter.h src/common/FixedVector.h +src/RubberBandStretcher.o: src/common/SingleThreadRingBuffer.h +src/RubberBandStretcher.o: src/common/HistogramFilter.h src/common/mathmisc.h +src/RubberBandStretcher.o: src/finer/Guide.h src/finer/Peak.h +src/RubberBandStretcher.o: src/finer/PhaseAdvance.h +src/RubberBandStretcher.o: src/common/StretchCalculator.h src/common/Log.h +src/RubberBandStretcher.o: src/common/Resampler.h src/common/FixedVector.h +src/RubberBandStretcher.o: src/common/VectorOpsComplex.h +src/faster/AudioCurveCalculator.o: src/faster/AudioCurveCalculator.h +src/faster/AudioCurveCalculator.o: src/common/sysutils.h +src/faster/CompoundAudioCurve.o: src/faster/CompoundAudioCurve.h +src/faster/CompoundAudioCurve.o: src/faster/PercussiveAudioCurve.h +src/faster/CompoundAudioCurve.o: src/faster/AudioCurveCalculator.h +src/faster/CompoundAudioCurve.o: src/common/sysutils.h +src/faster/CompoundAudioCurve.o: src/faster/HighFrequencyAudioCurve.h +src/faster/CompoundAudioCurve.o: src/common/SampleFilter.h +src/faster/CompoundAudioCurve.o: src/common/MovingMedian.h +src/faster/CompoundAudioCurve.o: src/common/SampleFilter.h +src/faster/CompoundAudioCurve.o: src/common/FixedVector.h +src/faster/CompoundAudioCurve.o: src/common/Allocators.h +src/faster/CompoundAudioCurve.o: src/common/VectorOps.h src/common/sysutils.h +src/faster/CompoundAudioCurve.o: src/common/SingleThreadRingBuffer.h +src/faster/HighFrequencyAudioCurve.o: src/faster/HighFrequencyAudioCurve.h +src/faster/HighFrequencyAudioCurve.o: src/faster/AudioCurveCalculator.h +src/faster/HighFrequencyAudioCurve.o: src/common/sysutils.h +src/faster/SilentAudioCurve.o: src/faster/SilentAudioCurve.h +src/faster/SilentAudioCurve.o: src/faster/AudioCurveCalculator.h +src/faster/SilentAudioCurve.o: src/common/sysutils.h +src/faster/PercussiveAudioCurve.o: src/faster/PercussiveAudioCurve.h +src/faster/PercussiveAudioCurve.o: src/faster/AudioCurveCalculator.h +src/faster/PercussiveAudioCurve.o: src/common/sysutils.h +src/faster/PercussiveAudioCurve.o: src/common/Allocators.h +src/faster/PercussiveAudioCurve.o: src/common/VectorOps.h +src/faster/R2Stretcher.o: src/faster/R2Stretcher.h +src/faster/R2Stretcher.o: rubberband/RubberBandStretcher.h +src/faster/R2Stretcher.o: src/common/Window.h src/common/sysutils.h +src/faster/R2Stretcher.o: src/common/VectorOps.h src/common/Allocators.h +src/faster/R2Stretcher.o: src/common/FFT.h src/common/RingBuffer.h +src/faster/R2Stretcher.o: src/common/Scavenger.h src/common/Thread.h +src/faster/R2Stretcher.o: src/common/Thread.h src/common/Log.h +src/faster/R2Stretcher.o: src/common/sysutils.h src/faster/SincWindow.h +src/faster/R2Stretcher.o: src/common/VectorOps.h src/common/Allocators.h +src/faster/R2Stretcher.o: src/faster/CompoundAudioCurve.h +src/faster/R2Stretcher.o: src/faster/PercussiveAudioCurve.h +src/faster/R2Stretcher.o: src/faster/AudioCurveCalculator.h +src/faster/R2Stretcher.o: src/faster/HighFrequencyAudioCurve.h +src/faster/R2Stretcher.o: src/common/SampleFilter.h +src/faster/R2Stretcher.o: src/faster/SilentAudioCurve.h +src/faster/R2Stretcher.o: src/faster/StretcherChannelData.h +src/faster/R2Stretcher.o: src/common/StretchCalculator.h src/common/Log.h +src/faster/R2Stretcher.o: src/common/Resampler.h src/common/Profiler.h +src/faster/StretcherChannelData.o: src/faster/StretcherChannelData.h +src/faster/StretcherChannelData.o: src/faster/R2Stretcher.h +src/faster/StretcherChannelData.o: rubberband/RubberBandStretcher.h +src/faster/StretcherChannelData.o: src/common/Window.h src/common/sysutils.h +src/faster/StretcherChannelData.o: src/common/VectorOps.h +src/faster/StretcherChannelData.o: src/common/Allocators.h src/common/FFT.h +src/faster/StretcherChannelData.o: src/common/RingBuffer.h +src/faster/StretcherChannelData.o: src/common/Scavenger.h src/common/Thread.h +src/faster/StretcherChannelData.o: src/common/Thread.h src/common/Log.h +src/faster/StretcherChannelData.o: src/common/sysutils.h +src/faster/StretcherChannelData.o: src/faster/SincWindow.h +src/faster/StretcherChannelData.o: src/common/VectorOps.h +src/faster/StretcherChannelData.o: src/common/Allocators.h +src/faster/StretcherChannelData.o: src/faster/CompoundAudioCurve.h +src/faster/StretcherChannelData.o: src/faster/PercussiveAudioCurve.h +src/faster/StretcherChannelData.o: src/faster/AudioCurveCalculator.h +src/faster/StretcherChannelData.o: src/faster/HighFrequencyAudioCurve.h +src/faster/StretcherChannelData.o: src/common/SampleFilter.h +src/faster/StretcherChannelData.o: src/common/Resampler.h +src/faster/StretcherProcess.o: src/faster/R2Stretcher.h +src/faster/StretcherProcess.o: rubberband/RubberBandStretcher.h +src/faster/StretcherProcess.o: src/common/Window.h src/common/sysutils.h +src/faster/StretcherProcess.o: src/common/VectorOps.h src/common/Allocators.h +src/faster/StretcherProcess.o: src/common/FFT.h src/common/RingBuffer.h +src/faster/StretcherProcess.o: src/common/Scavenger.h src/common/Thread.h +src/faster/StretcherProcess.o: src/common/Thread.h src/common/Log.h +src/faster/StretcherProcess.o: src/common/sysutils.h src/faster/SincWindow.h +src/faster/StretcherProcess.o: src/common/VectorOps.h src/common/Allocators.h +src/faster/StretcherProcess.o: src/faster/CompoundAudioCurve.h +src/faster/StretcherProcess.o: src/faster/PercussiveAudioCurve.h +src/faster/StretcherProcess.o: src/faster/AudioCurveCalculator.h +src/faster/StretcherProcess.o: src/faster/HighFrequencyAudioCurve.h +src/faster/StretcherProcess.o: src/common/SampleFilter.h +src/faster/StretcherProcess.o: src/faster/StretcherChannelData.h +src/faster/StretcherProcess.o: src/common/StretchCalculator.h +src/faster/StretcherProcess.o: src/common/Log.h src/common/Resampler.h +src/faster/StretcherProcess.o: src/common/Profiler.h src/common/mathmisc.h +src/common/Allocators.o: src/common/Allocators.h src/common/VectorOps.h +src/common/Allocators.o: src/common/sysutils.h +src/common/BQResampler.o: src/common/BQResampler.h src/common/Allocators.h +src/common/BQResampler.o: src/common/VectorOps.h src/common/sysutils.h +src/common/FFT.o: src/common/FFT.h src/common/sysutils.h src/common/Thread.h +src/common/FFT.o: src/common/Profiler.h src/common/Allocators.h +src/common/FFT.o: src/common/VectorOps.h src/common/VectorOpsComplex.h +src/common/Log.o: src/common/Log.h +src/common/Profiler.o: src/common/Profiler.h src/common/sysutils.h +src/common/Profiler.o: src/common/Thread.h +src/common/Resampler.o: src/common/Resampler.h src/common/sysutils.h +src/common/Resampler.o: src/common/Allocators.h src/common/VectorOps.h +src/common/StretchCalculator.o: src/common/StretchCalculator.h +src/common/StretchCalculator.o: src/common/Log.h src/common/sysutils.h +src/common/sysutils.o: src/common/sysutils.h +src/common/Thread.o: src/common/Thread.h +src/finer/R3Stretcher.o: src/finer/R3Stretcher.h src/finer/BinSegmenter.h +src/finer/R3Stretcher.o: src/finer/BinClassifier.h src/common/Allocators.h +src/finer/R3Stretcher.o: src/common/MovingMedian.h src/common/SampleFilter.h +src/finer/R3Stretcher.o: src/common/FixedVector.h src/common/Allocators.h +src/finer/R3Stretcher.o: src/common/VectorOps.h src/common/sysutils.h +src/finer/R3Stretcher.o: src/common/SingleThreadRingBuffer.h +src/finer/R3Stretcher.o: src/common/RingBuffer.h src/common/HistogramFilter.h +src/finer/R3Stretcher.o: src/common/mathmisc.h src/finer/Guide.h +src/finer/R3Stretcher.o: src/common/Log.h src/finer/Peak.h +src/finer/R3Stretcher.o: src/finer/PhaseAdvance.h +src/finer/R3Stretcher.o: src/common/StretchCalculator.h src/common/Log.h +src/finer/R3Stretcher.o: src/common/Resampler.h src/common/FFT.h +src/finer/R3Stretcher.o: src/common/FixedVector.h src/common/Window.h +src/finer/R3Stretcher.o: src/common/VectorOpsComplex.h +src/finer/R3Stretcher.o: rubberband/RubberBandStretcher.h diff --git a/otherbuilds/Makefile.macos-universal b/otherbuilds/Makefile.macos-universal index aa03954..5513ff7 100644 --- a/otherbuilds/Makefile.macos-universal +++ b/otherbuilds/Makefile.macos-universal @@ -1,85 +1,51 @@ -CXX := clang++ -stdlib=libc++ +CXX := clang++ -stdlib=libc++ -std=c++11 CC := clang OPTFLAGS := -DNDEBUG -ffast-math -O3 -ftree-vectorize ARCHFLAGS := -arch arm64 -arch x86_64 -mmacosx-version-min=10.7 -CXXFLAGS := $(ARCHFLAGS) $(OPTFLAGS) -I. -Isrc -Irubberband -I/usr/local/include -DUSE_PTHREADS -DMALLOC_IS_ALIGNED -DHAVE_VDSP -DUSE_SPEEX -DNO_THREAD_CHECKS -DNO_TIMING +CXXFLAGS := $(ARCHFLAGS) $(OPTFLAGS) -I. -Isrc -Irubberband -DUSE_BQRESAMPLER -DHAVE_VDSP -DNO_THREAD_CHECKS -DUSE_PTHREADS -DNO_TIMING -DMALLOC_IS_ALIGNED -DNDEBUG CFLAGS := $(ARCHFLAGS) $(OPTFLAGS) -AR := ar -MKDIR := mkdir +AR := ar +MKDIR := mkdir -LIBNAME := librubberband +LIBNAME := librubberband -STATIC_TARGET := lib/$(LIBNAME).a +STATIC_TARGET := lib/$(LIBNAME).a default: $(STATIC_TARGET) - all: $(STATIC_TARGET) - static: $(STATIC_TARGET) PUBLIC_INCLUDES := \ rubberband/rubberband-c.h \ rubberband/RubberBandStretcher.h -LIBRARY_INCLUDES := \ - src/StretcherChannelData.h \ - src/float_cast/float_cast.h \ - src/StretcherImpl.h \ - src/StretchCalculator.h \ - src/base/Profiler.h \ - src/base/RingBuffer.h \ - src/base/Scavenger.h \ - src/dsp/AudioCurveCalculator.h \ - src/audiocurves/CompoundAudioCurve.h \ - src/audiocurves/ConstantAudioCurve.h \ - src/audiocurves/HighFrequencyAudioCurve.h \ - src/audiocurves/PercussiveAudioCurve.h \ - src/audiocurves/SilentAudioCurve.h \ - src/audiocurves/SpectralDifferenceAudioCurve.h \ - src/dsp/Resampler.h \ - src/dsp/FFT.h \ - src/dsp/MovingMedian.h \ - src/dsp/SincWindow.h \ - src/dsp/Window.h \ - src/system/Allocators.h \ - src/system/Thread.h \ - src/system/VectorOps.h \ - src/system/VectorOpsComplex.h \ - src/system/sysutils.h - LIBRARY_SOURCES := \ src/rubberband-c.cpp \ src/RubberBandStretcher.cpp \ - src/StretcherProcess.cpp \ - src/StretchCalculator.cpp \ - src/base/Profiler.cpp \ - src/dsp/AudioCurveCalculator.cpp \ - src/audiocurves/CompoundAudioCurve.cpp \ - src/audiocurves/SpectralDifferenceAudioCurve.cpp \ - src/audiocurves/HighFrequencyAudioCurve.cpp \ - src/audiocurves/SilentAudioCurve.cpp \ - src/audiocurves/ConstantAudioCurve.cpp \ - src/audiocurves/PercussiveAudioCurve.cpp \ - src/dsp/Resampler.cpp \ - src/dsp/FFT.cpp \ - src/system/Allocators.cpp \ - src/system/sysutils.cpp \ - src/system/Thread.cpp \ - src/system/VectorOpsComplex.cpp \ - src/StretcherChannelData.cpp \ - src/StretcherImpl.cpp - -# For Speex resampler -- comment these lines out if not specifying USE_SPEEX -LIBRARY_INCLUDES := $(LIBRARY_INCLUDES) \ - src/speex/speex_resampler.h -LIBRARY_SOURCES := $(LIBRARY_SOURCES) \ - src/speex/resample.c + src/faster/AudioCurveCalculator.cpp \ + src/faster/CompoundAudioCurve.cpp \ + src/faster/HighFrequencyAudioCurve.cpp \ + src/faster/SilentAudioCurve.cpp \ + src/faster/PercussiveAudioCurve.cpp \ + src/faster/R2Stretcher.cpp \ + src/faster/StretcherChannelData.cpp \ + src/faster/StretcherProcess.cpp \ + src/common/Allocators.cpp \ + src/common/BQResampler.cpp \ + src/common/FFT.cpp \ + src/common/Log.cpp \ + src/common/Profiler.cpp \ + src/common/Resampler.cpp \ + src/common/StretchCalculator.cpp \ + src/common/sysutils.cpp \ + src/common/Thread.cpp \ + src/finer/R3Stretcher.cpp LIBRARY_OBJECTS := $(LIBRARY_SOURCES:.cpp=.o) LIBRARY_OBJECTS := $(LIBRARY_OBJECTS:.c=.o) @@ -105,71 +71,138 @@ depend: src/rubberband-c.o: rubberband/rubberband-c.h src/rubberband-c.o: rubberband/RubberBandStretcher.h -src/RubberBandStretcher.o: src/StretcherImpl.h -src/RubberBandStretcher.o: rubberband/RubberBandStretcher.h src/dsp/Window.h -src/RubberBandStretcher.o: src/dsp/FFT.h src/base/RingBuffer.h -src/RubberBandStretcher.o: src/base/Scavenger.h src/system/Thread.h -src/RubberBandStretcher.o: src/system/Thread.h src/system/sysutils.h -src/StretcherProcess.o: src/StretcherImpl.h rubberband/RubberBandStretcher.h -src/StretcherProcess.o: src/dsp/Window.h src/dsp/FFT.h src/base/RingBuffer.h -src/StretcherProcess.o: src/base/Scavenger.h src/system/Thread.h -src/StretcherProcess.o: src/system/Thread.h src/system/sysutils.h -src/StretcherProcess.o: src/audiocurves/PercussiveAudioCurve.h -src/StretcherProcess.o: src/dsp/AudioCurveCalculator.h -src/StretcherProcess.o: src/audiocurves/HighFrequencyAudioCurve.h -src/StretcherProcess.o: src/audiocurves/ConstantAudioCurve.h src/StretchCalculator.h -src/StretcherProcess.o: src/StretcherChannelData.h src/dsp/Resampler.h -src/StretcherProcess.o: src/base/Profiler.h src/system/VectorOps.h -src/StretcherProcess.o: src/system/sysutils.h -src/StretchCalculator.o: src/StretchCalculator.h src/system/sysutils.h -src/system/Thread.o: src/system/Thread.h -src/base/Profiler.o: src/base/Profiler.h src/system/sysutils.h -src/dsp/AudioCurveCalculator.o: src/dsp/AudioCurveCalculator.h -src/dsp/AudioCurveCalculator.o: src/system/sysutils.h -src/audiocurves/SpectralDifferenceAudioCurve.o: src/audiocurves/SpectralDifferenceAudioCurve.h -src/audiocurves/SpectralDifferenceAudioCurve.o: src/dsp/AudioCurveCalculator.h -src/audiocurves/SpectralDifferenceAudioCurve.o: src/system/sysutils.h -src/audiocurves/SpectralDifferenceAudioCurve.o: src/dsp/Window.h -src/audiocurves/SpectralDifferenceAudioCurve.o: src/system/VectorOps.h -src/audiocurves/SpectralDifferenceAudioCurve.o: src/system/sysutils.h -src/audiocurves/HighFrequencyAudioCurve.o: src/audiocurves/HighFrequencyAudioCurve.h -src/audiocurves/HighFrequencyAudioCurve.o: src/dsp/AudioCurveCalculator.h -src/audiocurves/HighFrequencyAudioCurve.o: src/system/sysutils.h -src/audiocurves/SilentAudioCurve.o: src/audiocurves/SilentAudioCurve.h -src/audiocurves/SilentAudioCurve.o: src/dsp/AudioCurveCalculator.h -src/audiocurves/SilentAudioCurve.o: src/system/sysutils.h -src/audiocurves/ConstantAudioCurve.o: src/audiocurves/ConstantAudioCurve.h -src/audiocurves/ConstantAudioCurve.o: src/dsp/AudioCurveCalculator.h -src/audiocurves/ConstantAudioCurve.o: src/system/sysutils.h -src/audiocurves/PercussiveAudioCurve.o: src/audiocurves/PercussiveAudioCurve.h -src/audiocurves/PercussiveAudioCurve.o: src/dsp/AudioCurveCalculator.h -src/audiocurves/PercussiveAudioCurve.o: src/system/sysutils.h src/system/VectorOps.h -src/audiocurves/PercussiveAudioCurve.o: src/system/sysutils.h -src/dsp/Resampler.o: src/dsp/Resampler.h src/system/sysutils.h -src/dsp/Resampler.o: src/base/Profiler.h -src/dsp/FFT.o: src/dsp/FFT.h src/system/sysutils.h src/system/Thread.h -src/dsp/FFT.o: src/base/Profiler.h src/system/VectorOps.h -src/dsp/FFT.o: src/system/sysutils.h -src/system/Allocators.o: src/system/Allocators.h src/system/VectorOps.h -src/system/Allocators.o: src/system/sysutils.h -src/system/sysutils.o: src/system/sysutils.h -src/StretcherChannelData.o: src/StretcherChannelData.h src/StretcherImpl.h -src/StretcherChannelData.o: rubberband/RubberBandStretcher.h src/dsp/Window.h -src/StretcherChannelData.o: src/dsp/FFT.h src/base/RingBuffer.h -src/StretcherChannelData.o: src/base/Scavenger.h src/system/Thread.h -src/StretcherChannelData.o: src/system/Thread.h src/system/sysutils.h -src/StretcherChannelData.o: src/dsp/Resampler.h src/system/Allocators.h -src/StretcherChannelData.o: src/system/VectorOps.h src/system/sysutils.h -src/StretcherImpl.o: src/StretcherImpl.h rubberband/RubberBandStretcher.h -src/StretcherImpl.o: src/dsp/Window.h src/dsp/FFT.h src/base/RingBuffer.h -src/StretcherImpl.o: src/base/Scavenger.h src/system/Thread.h src/system/Thread.h -src/StretcherImpl.o: src/system/sysutils.h src/audiocurves/PercussiveAudioCurve.h -src/StretcherImpl.o: src/dsp/AudioCurveCalculator.h -src/StretcherImpl.o: src/audiocurves/HighFrequencyAudioCurve.h -src/StretcherImpl.o: src/audiocurves/SpectralDifferenceAudioCurve.h src/dsp/Window.h -src/StretcherImpl.o: src/system/VectorOps.h src/system/sysutils.h -src/StretcherImpl.o: src/audiocurves/SilentAudioCurve.h src/audiocurves/ConstantAudioCurve.h -src/StretcherImpl.o: src/dsp/Resampler.h src/StretchCalculator.h -src/StretcherImpl.o: src/StretcherChannelData.h src/base/Profiler.h -main/main.o: rubberband/RubberBandStretcher.h src/system/sysutils.h -main/main.o: src/base/Profiler.h +src/RubberBandStretcher.o: src/faster/R2Stretcher.h +src/RubberBandStretcher.o: rubberband/RubberBandStretcher.h +src/RubberBandStretcher.o: src/common/Window.h src/common/sysutils.h +src/RubberBandStretcher.o: src/common/VectorOps.h src/common/Allocators.h +src/RubberBandStretcher.o: src/common/FFT.h src/common/RingBuffer.h +src/RubberBandStretcher.o: src/common/Scavenger.h src/common/Thread.h +src/RubberBandStretcher.o: src/common/Thread.h src/common/Log.h +src/RubberBandStretcher.o: src/common/sysutils.h src/faster/SincWindow.h +src/RubberBandStretcher.o: src/common/VectorOps.h src/common/Allocators.h +src/RubberBandStretcher.o: src/faster/CompoundAudioCurve.h +src/RubberBandStretcher.o: src/faster/PercussiveAudioCurve.h +src/RubberBandStretcher.o: src/faster/AudioCurveCalculator.h +src/RubberBandStretcher.o: src/faster/HighFrequencyAudioCurve.h +src/RubberBandStretcher.o: src/common/SampleFilter.h src/finer/R3Stretcher.h +src/RubberBandStretcher.o: src/finer/BinSegmenter.h src/finer/BinClassifier.h +src/RubberBandStretcher.o: src/common/MovingMedian.h +src/RubberBandStretcher.o: src/common/SampleFilter.h src/common/FixedVector.h +src/RubberBandStretcher.o: src/common/SingleThreadRingBuffer.h +src/RubberBandStretcher.o: src/common/HistogramFilter.h src/common/mathmisc.h +src/RubberBandStretcher.o: src/finer/Guide.h src/finer/Peak.h +src/RubberBandStretcher.o: src/finer/PhaseAdvance.h +src/RubberBandStretcher.o: src/common/StretchCalculator.h src/common/Log.h +src/RubberBandStretcher.o: src/common/Resampler.h src/common/FixedVector.h +src/RubberBandStretcher.o: src/common/VectorOpsComplex.h +src/faster/AudioCurveCalculator.o: src/faster/AudioCurveCalculator.h +src/faster/AudioCurveCalculator.o: src/common/sysutils.h +src/faster/CompoundAudioCurve.o: src/faster/CompoundAudioCurve.h +src/faster/CompoundAudioCurve.o: src/faster/PercussiveAudioCurve.h +src/faster/CompoundAudioCurve.o: src/faster/AudioCurveCalculator.h +src/faster/CompoundAudioCurve.o: src/common/sysutils.h +src/faster/CompoundAudioCurve.o: src/faster/HighFrequencyAudioCurve.h +src/faster/CompoundAudioCurve.o: src/common/SampleFilter.h +src/faster/CompoundAudioCurve.o: src/common/MovingMedian.h +src/faster/CompoundAudioCurve.o: src/common/SampleFilter.h +src/faster/CompoundAudioCurve.o: src/common/FixedVector.h +src/faster/CompoundAudioCurve.o: src/common/Allocators.h +src/faster/CompoundAudioCurve.o: src/common/VectorOps.h src/common/sysutils.h +src/faster/CompoundAudioCurve.o: src/common/SingleThreadRingBuffer.h +src/faster/HighFrequencyAudioCurve.o: src/faster/HighFrequencyAudioCurve.h +src/faster/HighFrequencyAudioCurve.o: src/faster/AudioCurveCalculator.h +src/faster/HighFrequencyAudioCurve.o: src/common/sysutils.h +src/faster/SilentAudioCurve.o: src/faster/SilentAudioCurve.h +src/faster/SilentAudioCurve.o: src/faster/AudioCurveCalculator.h +src/faster/SilentAudioCurve.o: src/common/sysutils.h +src/faster/PercussiveAudioCurve.o: src/faster/PercussiveAudioCurve.h +src/faster/PercussiveAudioCurve.o: src/faster/AudioCurveCalculator.h +src/faster/PercussiveAudioCurve.o: src/common/sysutils.h +src/faster/PercussiveAudioCurve.o: src/common/Allocators.h +src/faster/PercussiveAudioCurve.o: src/common/VectorOps.h +src/faster/R2Stretcher.o: src/faster/R2Stretcher.h +src/faster/R2Stretcher.o: rubberband/RubberBandStretcher.h +src/faster/R2Stretcher.o: src/common/Window.h src/common/sysutils.h +src/faster/R2Stretcher.o: src/common/VectorOps.h src/common/Allocators.h +src/faster/R2Stretcher.o: src/common/FFT.h src/common/RingBuffer.h +src/faster/R2Stretcher.o: src/common/Scavenger.h src/common/Thread.h +src/faster/R2Stretcher.o: src/common/Thread.h src/common/Log.h +src/faster/R2Stretcher.o: src/common/sysutils.h src/faster/SincWindow.h +src/faster/R2Stretcher.o: src/common/VectorOps.h src/common/Allocators.h +src/faster/R2Stretcher.o: src/faster/CompoundAudioCurve.h +src/faster/R2Stretcher.o: src/faster/PercussiveAudioCurve.h +src/faster/R2Stretcher.o: src/faster/AudioCurveCalculator.h +src/faster/R2Stretcher.o: src/faster/HighFrequencyAudioCurve.h +src/faster/R2Stretcher.o: src/common/SampleFilter.h +src/faster/R2Stretcher.o: src/faster/SilentAudioCurve.h +src/faster/R2Stretcher.o: src/faster/StretcherChannelData.h +src/faster/R2Stretcher.o: src/common/StretchCalculator.h src/common/Log.h +src/faster/R2Stretcher.o: src/common/Resampler.h src/common/Profiler.h +src/faster/StretcherChannelData.o: src/faster/StretcherChannelData.h +src/faster/StretcherChannelData.o: src/faster/R2Stretcher.h +src/faster/StretcherChannelData.o: rubberband/RubberBandStretcher.h +src/faster/StretcherChannelData.o: src/common/Window.h src/common/sysutils.h +src/faster/StretcherChannelData.o: src/common/VectorOps.h +src/faster/StretcherChannelData.o: src/common/Allocators.h src/common/FFT.h +src/faster/StretcherChannelData.o: src/common/RingBuffer.h +src/faster/StretcherChannelData.o: src/common/Scavenger.h src/common/Thread.h +src/faster/StretcherChannelData.o: src/common/Thread.h src/common/Log.h +src/faster/StretcherChannelData.o: src/common/sysutils.h +src/faster/StretcherChannelData.o: src/faster/SincWindow.h +src/faster/StretcherChannelData.o: src/common/VectorOps.h +src/faster/StretcherChannelData.o: src/common/Allocators.h +src/faster/StretcherChannelData.o: src/faster/CompoundAudioCurve.h +src/faster/StretcherChannelData.o: src/faster/PercussiveAudioCurve.h +src/faster/StretcherChannelData.o: src/faster/AudioCurveCalculator.h +src/faster/StretcherChannelData.o: src/faster/HighFrequencyAudioCurve.h +src/faster/StretcherChannelData.o: src/common/SampleFilter.h +src/faster/StretcherChannelData.o: src/common/Resampler.h +src/faster/StretcherProcess.o: src/faster/R2Stretcher.h +src/faster/StretcherProcess.o: rubberband/RubberBandStretcher.h +src/faster/StretcherProcess.o: src/common/Window.h src/common/sysutils.h +src/faster/StretcherProcess.o: src/common/VectorOps.h src/common/Allocators.h +src/faster/StretcherProcess.o: src/common/FFT.h src/common/RingBuffer.h +src/faster/StretcherProcess.o: src/common/Scavenger.h src/common/Thread.h +src/faster/StretcherProcess.o: src/common/Thread.h src/common/Log.h +src/faster/StretcherProcess.o: src/common/sysutils.h src/faster/SincWindow.h +src/faster/StretcherProcess.o: src/common/VectorOps.h src/common/Allocators.h +src/faster/StretcherProcess.o: src/faster/CompoundAudioCurve.h +src/faster/StretcherProcess.o: src/faster/PercussiveAudioCurve.h +src/faster/StretcherProcess.o: src/faster/AudioCurveCalculator.h +src/faster/StretcherProcess.o: src/faster/HighFrequencyAudioCurve.h +src/faster/StretcherProcess.o: src/common/SampleFilter.h +src/faster/StretcherProcess.o: src/faster/StretcherChannelData.h +src/faster/StretcherProcess.o: src/common/StretchCalculator.h +src/faster/StretcherProcess.o: src/common/Log.h src/common/Resampler.h +src/faster/StretcherProcess.o: src/common/Profiler.h src/common/mathmisc.h +src/common/Allocators.o: src/common/Allocators.h src/common/VectorOps.h +src/common/Allocators.o: src/common/sysutils.h +src/common/BQResampler.o: src/common/BQResampler.h src/common/Allocators.h +src/common/BQResampler.o: src/common/VectorOps.h src/common/sysutils.h +src/common/FFT.o: src/common/FFT.h src/common/sysutils.h src/common/Thread.h +src/common/FFT.o: src/common/Profiler.h src/common/Allocators.h +src/common/FFT.o: src/common/VectorOps.h src/common/VectorOpsComplex.h +src/common/Log.o: src/common/Log.h +src/common/Profiler.o: src/common/Profiler.h src/common/sysutils.h +src/common/Profiler.o: src/common/Thread.h +src/common/Resampler.o: src/common/Resampler.h src/common/sysutils.h +src/common/Resampler.o: src/common/Allocators.h src/common/VectorOps.h +src/common/StretchCalculator.o: src/common/StretchCalculator.h +src/common/StretchCalculator.o: src/common/Log.h src/common/sysutils.h +src/common/sysutils.o: src/common/sysutils.h +src/common/Thread.o: src/common/Thread.h +src/finer/R3Stretcher.o: src/finer/R3Stretcher.h src/finer/BinSegmenter.h +src/finer/R3Stretcher.o: src/finer/BinClassifier.h src/common/Allocators.h +src/finer/R3Stretcher.o: src/common/MovingMedian.h src/common/SampleFilter.h +src/finer/R3Stretcher.o: src/common/FixedVector.h src/common/Allocators.h +src/finer/R3Stretcher.o: src/common/VectorOps.h src/common/sysutils.h +src/finer/R3Stretcher.o: src/common/SingleThreadRingBuffer.h +src/finer/R3Stretcher.o: src/common/RingBuffer.h src/common/HistogramFilter.h +src/finer/R3Stretcher.o: src/common/mathmisc.h src/finer/Guide.h +src/finer/R3Stretcher.o: src/common/Log.h src/finer/Peak.h +src/finer/R3Stretcher.o: src/finer/PhaseAdvance.h +src/finer/R3Stretcher.o: src/common/StretchCalculator.h src/common/Log.h +src/finer/R3Stretcher.o: src/common/Resampler.h src/common/FFT.h +src/finer/R3Stretcher.o: src/common/FixedVector.h src/common/Window.h +src/finer/R3Stretcher.o: src/common/VectorOpsComplex.h +src/finer/R3Stretcher.o: rubberband/RubberBandStretcher.h diff --git a/otherbuilds/check.sh b/otherbuilds/check.sh index 0a3c3e5..47758a2 100755 --- a/otherbuilds/check.sh +++ b/otherbuilds/check.sh @@ -5,17 +5,17 @@ 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 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 + g++ -std=c++11 main/main.cpp lib/librubberband.a -I. -Isrc -o test -lsndfile -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 + g++ -O3 -std=c++11 main/main.cpp single/RubberBandSingle.cpp -o test_single -lsndfile echo " *** Running build from single-file source" ./test_single -V @@ -29,13 +29,13 @@ else 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 + c++ -std=c++11 main/main.cpp lib/librubberband.a -I. -Isrc -o test -lsndfile -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 + c++ -O3 -std=c++11 main/main.cpp single/RubberBandSingle.cpp -o test_single -lsndfile -framework Accelerate echo " *** Running build from single-file source" ./test_single -V diff --git a/otherbuilds/deploy/source.sh b/otherbuilds/deploy/source.sh index c072a51..f8f84d4 100755 --- a/otherbuilds/deploy/source.sh +++ b/otherbuilds/deploy/source.sh @@ -1,6 +1,6 @@ #!/bin/bash set -eu -version=$(grep '^ *version:' meson.build | head -1 | sed "s/^.*'\([0-9][0-9.]*\)'.*$/\1/") +version=$(meson introspect --projectinfo meson.build -i | grep '"version"' | sed -e 's/^.*: "//' -e 's/".*$//') check() { text="$1" echo -n "$text [yN] " @@ -45,6 +45,15 @@ echo "The CHANGELOG should start with a list of changes for this release." head -3 CHANGELOG check "The first three lines of the CHANGELOG are above. Are they correct?" +hgid=$(hg id | awk '{ print $1; }') +case "$hgid" in + *+) echo "ERROR: Working copy has been modified, please review and commit" + echo "as appropriate before continuing" + exit 1 + ;; + *);; +esac + echo check "All version checks passed. Continue?" diff --git a/otherbuilds/deploy/win.bat b/otherbuilds/deploy/win.bat index c0e3167..22dc658 100644 --- a/otherbuilds/deploy/win.bat +++ b/otherbuilds/deploy/win.bat @@ -34,6 +34,7 @@ if errorlevel 1 exit /b %errorlevel% cd build ren rubberband-program.exe rubberband.exe +ren rubberband-program-r3.exe rubberband-r3.exe set NAME=Christopher Cannam signtool sign /v /n "%NAME%" /t http://time.certum.pl /fd sha1 /a rubberband.exe if errorlevel 1 exit /b %errorlevel% @@ -43,6 +44,7 @@ set DIR=rubberband-%VERSION%-gpl-executable-windows del /q /s %DIR% mkdir %DIR% copy build\rubberband.exe %DIR% +copy build\rubberband-r3.exe %DIR% copy "c:\Program Files\libsndfile\bin\sndfile.dll" %DIR% copy COPYING %DIR%\COPYING.txt copy README.md %DIR%\README.txt diff --git a/otherbuilds/rubberband-library.vcxproj b/otherbuilds/rubberband-library.vcxproj index 8fbddaf..3f84bb1 100644 --- a/otherbuilds/rubberband-library.vcxproj +++ b/otherbuilds/rubberband-library.vcxproj @@ -77,7 +77,7 @@ Disabled ..;..\src;%(AdditionalIncludeDirectories) - __MSVC__;WIN32;_DEBUG;_LIB;NOMINMAX;_USE_MATH_DEFINES;USE_BUILTIN_FFT;USE_SPEEX;%(PreprocessorDefinitions) + __MSVC__;WIN32;_DEBUG;_LIB;NOMINMAX;_USE_MATH_DEFINES;USE_BUILTIN_FFT;USE_BQRESAMPLER;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL @@ -91,7 +91,7 @@ Disabled ..;..\src;%(AdditionalIncludeDirectories) - __MSVC__;WIN32;_DEBUG;_LIB;NOMINMAX;_USE_MATH_DEFINES;USE_BUILTIN_FFT;USE_SPEEX;%(PreprocessorDefinitions) + __MSVC__;WIN32;_DEBUG;_LIB;NOMINMAX;_USE_MATH_DEFINES;USE_BUILTIN_FFT;USE_BQRESAMPLER;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL @@ -109,7 +109,7 @@ Speed true ..;..\src;%(AdditionalIncludeDirectories) - __MSVC__;WIN32;NDEBUG;_LIB;NOMINMAX;_USE_MATH_DEFINES;USE_BUILTIN_FFT;NO_TIMING;USE_SPEEX;NO_THREAD_CHECKS;%(PreprocessorDefinitions) + __MSVC__;WIN32;NDEBUG;_LIB;NOMINMAX;_USE_MATH_DEFINES;USE_BUILTIN_FFT;NO_TIMING;USE_BQRESAMPLER;NO_THREAD_CHECKS;%(PreprocessorDefinitions) MultiThreadedDLL false StreamingSIMDExtensions @@ -127,7 +127,7 @@ Speed true ..;..\src;%(AdditionalIncludeDirectories) - __MSVC__;WIN32;NDEBUG;_LIB;NOMINMAX;_USE_MATH_DEFINES;USE_BUILTIN_FFT;NO_TIMING;USE_SPEEX;NO_THREAD_CHECKS;%(PreprocessorDefinitions) + __MSVC__;WIN32;NDEBUG;_LIB;NOMINMAX;_USE_MATH_DEFINES;USE_BUILTIN_FFT;NO_TIMING;USE_BQRESAMPLER;NO_THREAD_CHECKS;%(PreprocessorDefinitions) MultiThreadedDLL false StreamingSIMDExtensions @@ -139,55 +139,26 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/rubberband/RubberBandStretcher.h b/rubberband/RubberBandStretcher.h index 6e963b1..b5dc29a 100644 --- a/rubberband/RubberBandStretcher.h +++ b/rubberband/RubberBandStretcher.h @@ -24,9 +24,9 @@ #ifndef RUBBERBAND_STRETCHER_H #define RUBBERBAND_STRETCHER_H -#define RUBBERBAND_VERSION "2.0.2" +#define RUBBERBAND_VERSION "3.0.0" #define RUBBERBAND_API_MAJOR_VERSION 2 -#define RUBBERBAND_API_MINOR_VERSION 6 +#define RUBBERBAND_API_MINOR_VERSION 7 #undef RUBBERBAND_DLLEXPORT #ifdef _MSC_VER @@ -37,51 +37,89 @@ #include #include +#include +#include #include -/** - * @mainpage RubberBand - * - * The Rubber Band API is contained in the single class - * RubberBand::RubberBandStretcher. - * - * The Rubber Band stretcher supports two processing modes, offline - * and real-time. The choice of mode is fixed on construction. In - * offline mode, you must provide the audio block-by-block in two - * passes: in the first pass calling study(), in the second pass - * calling process() and receiving the output via retrieve(). In - * real-time mode, there is no study pass, just a single streaming - * pass in which the audio is passed to process() and output received - * via retrieve(). - * - * In real-time mode you can change the time and pitch ratios at any - * time, but in offline mode they are fixed and cannot be changed - * after the study pass has begun. (However, see setKeyFrameMap() for - * a way to do pre-planned variable time stretching in offline mode.) - * Offline mode typically produces slightly more precise results. - * - * Threading notes for real-time applications: - * - * Multiple instances of RubberBandStretcher may be created and used - * in separate threads concurrently. However, for any single instance - * of RubberBandStretcher, you may not call process() more than once - * concurrently, and you may not change the time or pitch ratio while - * a process() call is being executed (if the stretcher was created in - * "real-time mode"; in "offline mode" you can't change the ratios - * during use anyway). - * - * So you can run process() in its own thread if you like, but if you - * want to change ratios dynamically from a different thread, you will - * need some form of mutex in your code. Changing the time or pitch - * ratio is real-time safe except in extreme circumstances, so for - * most applications that may change these dynamically it probably - * makes most sense to do so from the same thread as calls process(), - * even if that is a real-time thread. - */ - namespace RubberBand { +/** + * @mainpage RubberBand + * + * ### Summary + * + * The Rubber Band Library API is contained in the single class + * RubberBand::RubberBandStretcher. + * + * The Rubber Band stretcher supports two processing modes, offline + * and real-time, and two processing "engines", known as the R2 or + * Faster engine and the R3 or Finer engine. The choices of processing + * mode and engine are fixed on construction: see + * RubberBandStretcher::RubberBandStretcher. The two engines work + * identically in API terms, and both of them support both offline and + * real-time modes as described below. + * + * ### Offline mode + * + * In offline mode, you must provide the audio block-by-block in + * two passes. In the first pass, call RubberBandStretcher::study() on + * each block; in the second pass, call RubberBandStretcher::process() + * on each block and receive the output via + * RubberBandStretcher::retrieve(). + * + * In offline mode, the time and pitch ratios are fixed as soon as the + * study pass has begun and cannot be changed afterwards. (But see + * RubberBandStretcher::setKeyFrameMap() for a way to do pre-planned + * variable time stretching in offline mode.) Offline mode also + * performs latency compensation so that the stretched result has an + * exact start and duration. + * + * ### Real-time mode + * + * In \b real-time mode, there is no study pass, just a single + * streaming pass in which the audio is passed to + * RubberBandStretcher::process() and output received via + * RubberBandStretcher::retrieve(). + * + * In real-time mode you can change the time and pitch ratios at any + * time. + * + * You may need to perform latency compensation in real-time mode; see + * RubberBandStretcher::getLatency() for details. + * + * Rubber Band Library is RT-safe when used in real-time mode with + * "normal" processing parameters. That is, it performs no allocation, + * locking, or blocking operations after initialisation during normal + * use, even when the time and pitch ratios change. There are certain + * exceptions that include error states and extremely rapid changes + * between extreme ratios, as well as the case in which more frames + * are passed to RubberBandStretcher::process() than the values + * returned by RubberBandStretcher::getSamplesRequired() or set using + * RubberBandStretcher::setMaxProcessSize(), when buffer reallocation + * may occur. See the latter function's documentation for + * details. Note that offline mode is never RT-safe. + * + * ### Thread safety + * + * Multiple instances of RubberBandStretcher may be created and used + * in separate threads concurrently. However, for any single instance + * of RubberBandStretcher, you may not call + * RubberBandStretcher::process() more than once concurrently, and you + * may not change the time or pitch ratio while a + * RubberBandStretcher::process() call is being executed (if the + * stretcher was created in "real-time mode"; in "offline mode" you + * can't change the ratios during use anyway). + * + * So you can run RubberBandStretcher::process() in its own thread if + * you like, but if you want to change ratios dynamically from a + * different thread, you will need some form of mutex in your code. + * Changing the time or pitch ratio is real-time safe except in + * extreme circumstances, so for most applications that may change + * these dynamically it probably makes most sense to do so from the + * same thread as calls RubberBandStretcher::process(), even if that + * is a real-time thread. + */ class RUBBERBAND_DLLEXPORT RubberBandStretcher { @@ -109,33 +147,43 @@ public: * non-real-time operation on seekable files: Offline; real-time * or streaming operation: RealTime. * - * 2. Flags prefixed \c OptionStretch control the profile used for - * variable timestretching. Rubber Band always adjusts the - * stretch profile to minimise stretching of busy broadband - * transient sounds, but the degree to which it does so is - * adjustable. These options may not be changed after - * construction. + * 2. Flags prefixed \c OptionEngine select the core Rubber Band + * processing engine to be used. These options may not be changed + * after construction. * - * \li \c OptionStretchElastic - Only meaningful in offline - * mode, and the default in that mode. The audio will be - * stretched at a variable rate, aimed at preserving the quality - * of transient sounds as much as possible. The timings of low - * activity regions between transients may be less exact than - * when the precise flag is set. - * - * \li \c OptionStretchPrecise - Although still using a variable - * stretch rate, the audio will be stretched so as to maintain - * as close as possible to a linear stretch ratio throughout. - * Timing may be better than when using \c OptionStretchElastic, at - * slight cost to the sound quality of transients. This setting - * is always used when running in real-time mode. + * \li \c OptionEngineFaster - Use the Rubber Band Library R2 + * (Faster) engine. This is the engine implemented in Rubber + * Band Library v1.x and v2.x, and it remains the default in + * newer versions. It uses substantially less CPU than the R3 + * engine and there are still many situations in which it is + * likely to be the more appropriate choice. + * + * \li \c OptionEngineFiner - Use the Rubber Band Library R3 + * (Finer) engine. This engine was introduced in Rubber Band + * Library v3.0. It produces higher-quality results than the R2 + * engine for most material, especially complex mixes, vocals + * and other sounds that have soft onsets and smooth pitch + * changes, and music with substantial bass content. However, it + * uses much more CPU power than the R2 engine. + * + * Important note: Consider calling getEngineVersion() after + * construction to make sure the engine you requested is + * active. That's not because engine selection can fail, but + * because Rubber Band Library ignores any unknown options + * supplied on construction - so a program that requests the R3 + * engine but ends up linked against an older version of the + * library (prior to v3.0) will silently use the R2 engine + * instead. Calling the v3.0 function getEngineVersion() will + * ensure a link failure in this situation instead, and supply a + * reassuring run-time check. * * 3. Flags prefixed \c OptionTransients control the component - * frequency phase-reset mechanism that may be used at transient - * points to provide clarity and realism to percussion and other - * significant transient sounds. These options may be changed - * after construction when running in real-time mode, but not when - * running in offline mode. + * frequency phase-reset mechanism in the R2 engine, that may be + * used at transient points to provide clarity and realism to + * percussion and other significant transient sounds. These + * options have no effect when using the R3 engine. These options + * may be changed after construction when running in real-time + * mode, but not when running in offline mode. * * \li \c OptionTransientsCrisp - Reset component phases at the * peak of each transient (the start of a significant note or @@ -160,9 +208,10 @@ public: * transients flags. * * 4. Flags prefixed \c OptionDetector control the type of - * transient detector used. These options may be changed - * after construction when running in real-time mode, but not when - * running in offline mode. + * transient detector used in the R2 engine. These options have + * no effect when using the R3 engine. These options may be + * changed after construction when running in real-time mode, but + * not when running in offline mode. * * \li \c OptionDetectorCompound - Use a general-purpose * transient detector which is likely to be good for most @@ -178,9 +227,10 @@ public: * piano music). * * 5. Flags prefixed \c OptionPhase control the adjustment of - * component frequency phases from one analysis window to the next - * during non-transient segments. These options may be changed at - * any time. + * component frequency phases in the R2 engine from one analysis + * window to the next during non-transient segments. These + * options have no effect when using the R3 engine. These options + * may be changed at any time. * * \li \c OptionPhaseLaminar - Adjust phases when stretching in * such a way as to try to retain the continuity of phase @@ -197,11 +247,13 @@ public: * construction. * * \li \c OptionThreadingAuto - Permit the stretcher to - * determine its own threading model. Usually this means using - * one processing thread per audio channel in offline mode if - * the stretcher is able to determine that more than one CPU is - * available, and one thread only in realtime mode. This is the - * default. + * determine its own threading model. In the R2 engine this + * means using one processing thread per audio channel in + * offline mode if the stretcher is able to determine that more + * than one CPU is available, and one thread only in realtime + * mode. The R3 engine does not currently have a multi-threaded + * mode, but if one is introduced in future, this option may use + * it. This is the default. * * \li \c OptionThreadingNever - Never use more than one thread. * @@ -210,9 +262,10 @@ public: * the check for multiple CPUs and instead assume it to be true. * * 7. Flags prefixed \c OptionWindow control the window size for - * FFT processing. The window size actually used will depend on - * many factors, but it can be influenced. These options may not - * be changed after construction. + * FFT processing in the R2 engine. (The window size actually + * used will depend on many factors, but it can be influenced.) + * These options have no effect when using the R3 engine. These + * options may not be changed after construction. * * \li \c OptionWindowStandard - Use the default window size. * The actual size will vary depending on other parameters. @@ -228,8 +281,9 @@ public: * clarity and timing. * * 8. Flags prefixed \c OptionSmoothing control the use of - * window-presum FFT and time-domain smoothing. These options may - * not be changed after construction. + * window-presum FFT and time-domain smoothing in the R2 + * engine. These options have no effect when using the R3 engine. + * These options may not be changed after construction. * * \li \c OptionSmoothingOff - Do not use time-domain smoothing. * This is the default. @@ -241,8 +295,9 @@ public: * OptionWindowShort. * * 9. Flags prefixed \c OptionFormant control the handling of - * formant shape (spectral envelope) when pitch-shifting. These - * options may be changed at any time. + * formant shape (spectral envelope) when pitch-shifting. These + * options affect both the R2 and R3 engines. These options may + * be changed at any time. * * \li \c OptionFormantShifted - Apply no special formant * processing. The spectral envelope will be pitch shifted as @@ -254,9 +309,10 @@ public: * perceived pitch profile of the voice or instrument. * * 10. Flags prefixed \c OptionPitch control the method used for - * pitch shifting. These options may be changed at any time. - * They are only effective in realtime mode; in offline mode, the - * pitch-shift method is fixed. + * pitch shifting in the R2 engine. These options have no effect + * when using the R3 engine. These options may be changed at any + * time. They are only effective in realtime mode; in offline + * mode, the pitch-shift method is fixed. * * \li \c OptionPitchHighSpeed - Use a method with a CPU cost * that is relatively moderate and predictable. This may @@ -274,9 +330,10 @@ public: * 1.0 pitch scale in real-time; it also consumes more CPU than * the others in the case where the pitch scale is exactly 1.0. * - * 11. Flags prefixed \c OptionChannels control the method used for - * processing two-channel audio. These options may not be changed - * after construction. + * 11. Flags prefixed \c OptionChannels control the method used + * for processing two-channel audio in the R2 engine. These + * options have no effect when using the R3 engine. These options + * may not be changed after construction. * * \li \c OptionChannelsApart - Each channel is processed * individually, though timing is synchronised and phases are @@ -293,15 +350,18 @@ public: * setting). This usually leads to better focus in the centre * but a loss of stereo space and width. Any channels beyond * the first two are processed individually. + * + * Finally, flags prefixed \c OptionStretch are obsolete flags + * provided for backward compatibility only. They are ignored by + * the stretcher. */ - enum Option { OptionProcessOffline = 0x00000000, OptionProcessRealTime = 0x00000001, - OptionStretchElastic = 0x00000000, - OptionStretchPrecise = 0x00000010, + OptionStretchElastic = 0x00000000, // obsolete + OptionStretchPrecise = 0x00000010, // obsolete OptionTransientsCrisp = 0x00000000, OptionTransientsMixed = 0x00000100, @@ -333,11 +393,18 @@ public: OptionPitchHighConsistency = 0x04000000, OptionChannelsApart = 0x00000000, - OptionChannelsTogether = 0x10000000 + OptionChannelsTogether = 0x10000000, + + OptionEngineFaster = 0x00000000, + OptionEngineFiner = 0x20000000 // n.b. Options is int, so we must stop before 0x80000000 }; + /** + * A bitwise OR of values from the RubberBandStretcher::Option + * enum. + */ typedef int Options; enum PresetOption { @@ -345,6 +412,37 @@ public: PercussiveOptions = 0x00102000 }; + /** + * Interface for log callbacks that may optionally be provided to + * the stretcher on construction. + * + * If a Logger is provided, the stretcher will call one of these + * functions instead of sending output to \c cerr when there is + * something to report. This allows debug output to be diverted to + * an application's logging facilities, and/or handled in an + * RT-safe way. See setDebugLevel() for details about how and when + * RubberBandStretcher reports something in this way. + * + * The message text passed to each of these log functions is a + * C-style string with no particular guaranteed lifespan. If you + * need to retain it, copy it before returning. Do not free it. + * + * @see setDebugLevel + * @see setDefaultDebugLevel + */ + struct Logger { + /// Receive a log message with no numeric values. + virtual void log(const char *) = 0; + + /// Receive a log message and one accompanying numeric value. + virtual void log(const char *, double) = 0; + + /// Receive a log message and two accompanying numeric values. + virtual void log(const char *, double, double) = 0; + + virtual ~Logger() { } + }; + /** * Construct a time and pitch stretcher object to run at the given * sample rate, with the given number of channels. @@ -375,6 +473,30 @@ public: Options options = DefaultOptions, double initialTimeRatio = 1.0, double initialPitchScale = 1.0); + + /** + * Construct a time and pitch stretcher object with a custom debug + * logger. This may be useful for debugging if the default logger + * output (which simply goes to \c cerr) is not visible in the + * runtime environment, or if the application has a standard or + * more realtime-appropriate logging mechanism. + * + * See the documentation for the other constructor above for + * details of the arguments other than the logger. + * + * Note that although the supplied logger gets to decide what to + * do with log messages, the separately-set debug level (see + * setDebugLevel() and setDefaultDebugLevel()) still determines + * whether any given debug message is sent to the logger in the + * first place. + */ + RubberBandStretcher(size_t sampleRate, + size_t channels, + std::shared_ptr logger, + Options options = DefaultOptions, + double initialTimeRatio = 1.0, + double initialPitchScale = 1.0); + ~RubberBandStretcher(); /** @@ -384,6 +506,15 @@ public: */ void reset(); + /** + * Return the active internal engine version, according to the \c + * OptionEngine flag supplied on construction. This will return 2 + * for the R2 (Faster) engine or 3 for the R3 (Finer) engine. + * + * This function was added in Rubber Band Library v3.0. + */ + int getEngineVersion() const; + /** * Set the time ratio for the stretcher. This is the ratio of * stretched to unstretched duration -- not tempo. For example, a @@ -434,6 +565,36 @@ public: */ void setPitchScale(double scale); + /** + * Set a pitch scale for the vocal formant envelope separately + * from the overall pitch scale. This is a ratio of target + * frequency to source frequency. For example, a ratio of 2.0 + * would shift the formant envelope up by one octave; 0.5 down by + * one octave; or 1.0 leave the formant unaffected. + * + * By default this is set to the special value of 0.0, which + * causes the scale to be calculated automatically. It will be + * treated as 1.0 / the pitch scale if OptionFormantPreserved is + * specified, or 1.0 for OptionFormantShifted. + * + * Conversely, if this is set to a value other than the default + * 0.0, formant shifting will happen regardless of the state of + * the OptionFormantPreserved/OptionFormantShifted option. + * + * This function is provided for special effects only. You do not + * need to call it for ordinary pitch shifting, with or without + * formant preservation - just specify or omit the + * OptionFormantPreserved option as appropriate. Use this function + * only if you want to shift formants by a distance other than + * that of the overall pitch shift. + * + * This function is supported only in the R3 (OptionEngineFiner) + * engine. It has no effect in R2 (OptionEngineFaster). + * + * This function was added in Rubber Band Library v3.0. + */ + void setFormantScale(double scale); + /** * Return the last time ratio value that was set (either on * construction or with setTimeRatio()). @@ -446,6 +607,18 @@ public: */ double getPitchScale() const; + /** + * Return the last formant scaling ratio that was set with + * setFormantScale, or 0.0 if the default automatic scaling is in + * effect. + * + * This function is supported only in the R3 (OptionEngineFiner) + * engine. It always returns 0.0 in R2 (OptionEngineFaster). + * + * This function was added in Rubber Band Library v3.0. + */ + double getFormantScale() const; + /** * Return the output delay or latency of the stretcher. This is * the number of audio samples that one would have to discard at @@ -467,10 +640,16 @@ public: size_t getLatency() const; /** - * Change an OptionTransients configuration setting. This may be + * Return the number of channels this stretcher was constructed + * with. + */ + size_t getChannelCount() const; + + /** + * Change an OptionTransients configuration setting. This may be * called at any time in RealTime mode. It may not be called in * Offline mode (for which the transients option is fixed on - * construction). + * construction). This has no effect when using the R3 engine. */ void setTransientsOption(Options options); @@ -478,13 +657,14 @@ public: * Change an OptionDetector configuration setting. This may be * called at any time in RealTime mode. It may not be called in * Offline mode (for which the detector option is fixed on - * construction). + * construction). This has no effect when using the R3 engine. */ void setDetectorOption(Options options); /** * Change an OptionPhase configuration setting. This may be - * called at any time in any mode. + * called at any time in any mode. This has no effect when using + * the R3 engine. * * Note that if running multi-threaded in Offline mode, the change * may not take effect immediately if processing is already under @@ -506,7 +686,7 @@ public: * Change an OptionPitch configuration setting. This may be * called at any time in RealTime mode. It may not be called in * Offline mode (for which the pitch option is fixed on - * construction). + * construction). This has no effect when using the R3 engine. */ void setPitchOption(Options options); @@ -711,7 +891,8 @@ public: /** * Retrieve the value of the internal input block increment value. * - * This function is provided for diagnostic purposes only. + * This function is provided for diagnostic purposes only and + * supported only with the R2 engine. */ size_t getInputIncrement() const; @@ -722,7 +903,8 @@ public: * retrieve any output increments that have accumulated since the * last call to getOutputIncrements, to a limit of 16. * - * This function is provided for diagnostic purposes only. + * This function is provided for diagnostic purposes only and + * supported only with the R2 engine. */ std::vector getOutputIncrements() const; @@ -733,7 +915,8 @@ public: * retrieve any phase reset points that have accumulated since the * last call to getPhaseResetCurve, to a limit of 16. * - * This function is provided for diagnostic purposes only. + * This function is provided for diagnostic purposes only and + * supported only with the R2 engine. */ std::vector getPhaseResetCurve() const; @@ -743,31 +926,52 @@ public: * provided the stretch profile has been calculated. In realtime * mode, return an empty sequence. * - * This function is provided for diagnostic purposes only. + * This function is provided for diagnostic purposes only and + * supported only with the R2 engine. */ std::vector getExactTimePoints() const; - /** - * Return the number of channels this stretcher was constructed - * with. - */ - size_t getChannelCount() const; - /** * Force the stretcher to calculate a stretch profile. Normally * this happens automatically for the first process() call in * offline mode. * - * This function is provided for diagnostic purposes only. + * This function is provided for diagnostic purposes only and + * supported only with the R2 engine. */ void calculateStretch(); /** - * Set the level of debug output. The value may be from 0 (errors - * only) to 3 (very verbose, with audible ticks in the output at - * phase reset points). The default is whatever has been set - * using setDefaultDebugLevel, or 0 if that function has not been + * Set the level of debug output. The supported values are: + * + * 0. Report errors only. + * + * 1. Report some information on construction and ratio + * change. Nothing is reported during normal processing unless + * something changes. + * + * 2. Report a significant amount of information about ongoing + * stretch calculations during normal processing. + * + * 3. Report a large amount of information and also (in the R2 + * engine) add audible ticks to the output at phase reset + * points. This is seldom useful. + * + * The default is whatever has been set using + * setDefaultDebugLevel(), or 0 if that function has not been * called. + * + * All output goes to \c cerr unless a custom + * RubberBandStretcher::Logger has been provided on + * construction. Because writing to \c cerr is not RT-safe, only + * debug level 0 is RT-safe in normal use by default. Debug levels + * 0 and 1 use only C-string constants as debug messages, so they + * are RT-safe if your custom logger is RT-safe. Levels 2 and 3 + * are not guaranteed to be RT-safe in any conditions as they may + * construct messages by allocation. + * + * @see Logger + * @see setDefaultDebugLevel */ void setDebugLevel(int level); @@ -782,6 +986,9 @@ public: protected: class Impl; Impl *m_d; + + RubberBandStretcher(const RubberBandStretcher &) =delete; + RubberBandStretcher &operator=(const RubberBandStretcher &) =delete; }; } diff --git a/rubberband/rubberband-c.h b/rubberband/rubberband-c.h index 6c3a4e9..8aea543 100644 --- a/rubberband/rubberband-c.h +++ b/rubberband/rubberband-c.h @@ -28,9 +28,9 @@ extern "C" { #endif -#define RUBBERBAND_VERSION "2.0.2" +#define RUBBERBAND_VERSION "3.0.0" #define RUBBERBAND_API_MAJOR_VERSION 2 -#define RUBBERBAND_API_MINOR_VERSION 6 +#define RUBBERBAND_API_MINOR_VERSION 7 #undef RB_EXTERN #ifdef _MSC_VER @@ -57,8 +57,8 @@ enum RubberBandOption { RubberBandOptionProcessOffline = 0x00000000, RubberBandOptionProcessRealTime = 0x00000001, - RubberBandOptionStretchElastic = 0x00000000, - RubberBandOptionStretchPrecise = 0x00000010, + RubberBandOptionStretchElastic = 0x00000000, // obsolete + RubberBandOptionStretchPrecise = 0x00000010, // obsolete RubberBandOptionTransientsCrisp = 0x00000000, RubberBandOptionTransientsMixed = 0x00000100, @@ -90,7 +90,10 @@ enum RubberBandOption { RubberBandOptionPitchHighConsistency = 0x04000000, RubberBandOptionChannelsApart = 0x00000000, - RubberBandOptionChannelsTogether = 0x10000000 + RubberBandOptionChannelsTogether = 0x10000000, + + RubberBandOptionEngineFaster = 0x00000000, + RubberBandOptionEngineFiner = 0x20000000 }; typedef int RubberBandOptions; @@ -108,12 +111,17 @@ RB_EXTERN void rubberband_delete(RubberBandState); RB_EXTERN void rubberband_reset(RubberBandState); +RB_EXTERN int rubberband_get_engine_version(RubberBandState); + RB_EXTERN void rubberband_set_time_ratio(RubberBandState, double ratio); RB_EXTERN void rubberband_set_pitch_scale(RubberBandState, double scale); RB_EXTERN double rubberband_get_time_ratio(const RubberBandState); RB_EXTERN double rubberband_get_pitch_scale(const RubberBandState); +RB_EXTERN void rubberband_set_formant_scale(RubberBandState, double scale); +RB_EXTERN double rubberband_get_formant_scale(const RubberBandState); + RB_EXTERN unsigned int rubberband_get_latency(const RubberBandState); RB_EXTERN void rubberband_set_transients_option(RubberBandState, RubberBandOptions options); diff --git a/single/RubberBandSingle.cpp b/single/RubberBandSingle.cpp index 422e8f5..91d78d0 100644 --- a/single/RubberBandSingle.cpp +++ b/single/RubberBandSingle.cpp @@ -56,25 +56,25 @@ #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/faster/AudioCurveCalculator.cpp" +#include "../src/faster/CompoundAudioCurve.cpp" +#include "../src/faster/HighFrequencyAudioCurve.cpp" +#include "../src/faster/SilentAudioCurve.cpp" +#include "../src/faster/PercussiveAudioCurve.cpp" +#include "../src/common/Log.cpp" +#include "../src/common/Profiler.cpp" +#include "../src/common/FFT.cpp" +#include "../src/common/Resampler.cpp" +#include "../src/common/BQResampler.cpp" +#include "../src/common/Allocators.cpp" +#include "../src/common/StretchCalculator.cpp" +#include "../src/common/sysutils.cpp" +#include "../src/common/Thread.cpp" +#include "../src/faster/StretcherChannelData.cpp" +#include "../src/faster/R2Stretcher.cpp" +#include "../src/faster/StretcherProcess.cpp" +#include "../src/finer/R3Stretcher.cpp" +#include "../src/RubberBandStretcher.cpp" #include "../src/rubberband-c.cpp" diff --git a/src/RubberBandStretcher.cpp b/src/RubberBandStretcher.cpp index 394cc03..a2a6ee4 100644 --- a/src/RubberBandStretcher.cpp +++ b/src/RubberBandStretcher.cpp @@ -21,18 +21,336 @@ you must obtain a valid commercial licence before doing so. */ -#include "StretcherImpl.h" +#include "faster/R2Stretcher.h" +#include "finer/R3Stretcher.h" +#include namespace RubberBand { +class RubberBandStretcher::Impl +{ + R2Stretcher *m_r2; + R3Stretcher *m_r3; + + class CerrLogger : public RubberBandStretcher::Logger { + public: + void log(const char *message) override { + std::cerr << "RubberBand: " << message << "\n"; + } + void log(const char *message, double arg0) override { + auto prec = std::cerr.precision(); + std::cerr.precision(10); + std::cerr << "RubberBand: " << message << ": " << arg0 << "\n"; + std::cerr.precision(prec); + } + void log(const char *message, double arg0, double arg1) override { + auto prec = std::cerr.precision(); + std::cerr.precision(10); + std::cerr << "RubberBand: " << message + << ": (" << arg0 << ", " << arg1 << ")" << "\n"; + std::cerr.precision(prec); + } + }; + + Log makeRBLog(std::shared_ptr logger) { + if (logger) { + return Log( + [=](const char *message) { + logger->log(message); + }, + [=](const char *message, double arg0) { + logger->log(message, arg0); + }, + [=](const char *message, double arg0, double arg1) { + logger->log(message, arg0, arg1); + } + ); + } else { + return makeRBLog(std::shared_ptr + (new CerrLogger())); + } + } + +public: + Impl(size_t sampleRate, size_t channels, Options options, + std::shared_ptr logger, + double initialTimeRatio, double initialPitchScale) : + m_r2 (!(options & OptionEngineFiner) ? + new R2Stretcher(sampleRate, channels, options, + initialTimeRatio, initialPitchScale, + makeRBLog(logger)) + : nullptr), + m_r3 ((options & OptionEngineFiner) ? + new R3Stretcher(R3Stretcher::Parameters + (double(sampleRate), channels, options), + initialTimeRatio, initialPitchScale, + makeRBLog(logger)) + : nullptr) + { + } + + ~Impl() + { + delete m_r2; + delete m_r3; + } + + int getEngineVersion() const + { + if (m_r3) return 3; + else return 2; + } + + void reset() + { + if (m_r2) m_r2->reset(); + else m_r3->reset(); + } + + RTENTRY__ + void + setTimeRatio(double ratio) + { + if (m_r2) m_r2->setTimeRatio(ratio); + else m_r3->setTimeRatio(ratio); + } + + RTENTRY__ + void + setPitchScale(double scale) + { + if (m_r2) m_r2->setPitchScale(scale); + else m_r3->setPitchScale(scale); + } + + RTENTRY__ + void + setFormantScale(double scale) + { + //!!! + if (m_r3) m_r3->setFormantScale(scale); + } + + RTENTRY__ + double + getTimeRatio() const + { + if (m_r2) return m_r2->getTimeRatio(); + else return m_r3->getTimeRatio(); + } + + RTENTRY__ + double + getPitchScale() const + { + if (m_r2) return m_r2->getPitchScale(); + else return m_r3->getPitchScale(); + } + + RTENTRY__ + double + getFormantScale() const + { + //!!! + if (m_r2) return 0.0; + else return m_r3->getFormantScale(); + } + + RTENTRY__ + size_t + getLatency() const + { + if (m_r2) return m_r2->getLatency(); + else return m_r3->getLatency(); + } + +//!!! review all these + + RTENTRY__ + void + setTransientsOption(Options options) + { + if (m_r2) m_r2->setTransientsOption(options); + } + + RTENTRY__ + void + setDetectorOption(Options options) + { + if (m_r2) m_r2->setDetectorOption(options); + } + + RTENTRY__ + void + setPhaseOption(Options options) + { + if (m_r2) m_r2->setPhaseOption(options); + } + + RTENTRY__ + void + setFormantOption(Options options) + { + if (m_r2) m_r2->setFormantOption(options); + else if (m_r3) m_r3->setFormantOption(options); + } + + RTENTRY__ + void + setPitchOption(Options options) + { + if (m_r2) m_r2->setPitchOption(options); + } + + void + setExpectedInputDuration(size_t samples) + { + if (m_r2) m_r2->setExpectedInputDuration(samples); + //!!! perhaps also for R3 + } + + void + setMaxProcessSize(size_t samples) + { + if (m_r2) m_r2->setMaxProcessSize(samples); + else m_r3->setMaxProcessSize(samples); + } + + void + setKeyFrameMap(const std::map &mapping) + { + if (m_r2) m_r2->setKeyFrameMap(mapping); + else m_r3->setKeyFrameMap(mapping); + } + + RTENTRY__ + size_t + getSamplesRequired() const + { + if (m_r2) return m_r2->getSamplesRequired(); + else return m_r3->getSamplesRequired(); + } + + void + study(const float *const *input, size_t samples, + bool final) + { + if (m_r2) m_r2->study(input, samples, final); + else m_r3->study(input, samples, final); + } + + RTENTRY__ + void + process(const float *const *input, size_t samples, + bool final) + { + if (m_r2) m_r2->process(input, samples, final); + else m_r3->process(input, samples, final); + } + + RTENTRY__ + int + available() const + { + if (m_r2) return m_r2->available(); + else return m_r3->available(); + } + + RTENTRY__ + size_t + retrieve(float *const *output, size_t samples) const + { + if (m_r2) return m_r2->retrieve(output, samples); + else return m_r3->retrieve(output, samples); + } + + float + getFrequencyCutoff(int n) const + { + if (m_r2) return m_r2->getFrequencyCutoff(n); + else return {}; + } + + void + setFrequencyCutoff(int n, float f) + { + if (m_r2) m_r2->setFrequencyCutoff(n, f); + } + + size_t + getInputIncrement() const + { + if (m_r2) return m_r2->getInputIncrement(); + else return {}; + } + + std::vector + getOutputIncrements() const + { + if (m_r2) return m_r2->getOutputIncrements(); + else return {}; + } + + std::vector + getPhaseResetCurve() const + { + if (m_r2) return m_r2->getPhaseResetCurve(); + else return {}; + } + + std::vector + getExactTimePoints() const + { + if (m_r2) return m_r2->getExactTimePoints(); + else return {}; + } + + RTENTRY__ + size_t + getChannelCount() const + { + if (m_r2) return m_r2->getChannelCount(); + else return m_r3->getChannelCount(); + } + + void + calculateStretch() + { + if (m_r2) m_r2->calculateStretch(); + } + + void + setDebugLevel(int level) + { + if (m_r2) m_r2->setDebugLevel(level); + else m_r3->setDebugLevel(level); + } + + static void + setDefaultDebugLevel(int level) + { + Log::setDefaultDebugLevel(level); + } +}; RubberBandStretcher::RubberBandStretcher(size_t sampleRate, size_t channels, Options options, double initialTimeRatio, double initialPitchScale) : - m_d(new Impl(sampleRate, channels, options, + m_d(new Impl(sampleRate, channels, options, nullptr, + initialTimeRatio, initialPitchScale)) +{ +} + +RubberBandStretcher::RubberBandStretcher(size_t sampleRate, + size_t channels, + std::shared_ptr logger, + Options options, + double initialTimeRatio, + double initialPitchScale) : + m_d(new Impl(sampleRate, channels, options, logger, initialTimeRatio, initialPitchScale)) { } @@ -48,60 +366,90 @@ RubberBandStretcher::reset() m_d->reset(); } +int +RubberBandStretcher::getEngineVersion() const +{ + return m_d->getEngineVersion(); +} + +RTENTRY__ void RubberBandStretcher::setTimeRatio(double ratio) { m_d->setTimeRatio(ratio); } +RTENTRY__ void RubberBandStretcher::setPitchScale(double scale) { m_d->setPitchScale(scale); } +RTENTRY__ +void +RubberBandStretcher::setFormantScale(double scale) +{ + m_d->setFormantScale(scale); +} + +RTENTRY__ double RubberBandStretcher::getTimeRatio() const { return m_d->getTimeRatio(); } +RTENTRY__ double RubberBandStretcher::getPitchScale() const { return m_d->getPitchScale(); } +RTENTRY__ +double +RubberBandStretcher::getFormantScale() const +{ + return m_d->getFormantScale(); +} + +RTENTRY__ size_t RubberBandStretcher::getLatency() const { return m_d->getLatency(); } +RTENTRY__ void RubberBandStretcher::setTransientsOption(Options options) { m_d->setTransientsOption(options); } +RTENTRY__ void RubberBandStretcher::setDetectorOption(Options options) { m_d->setDetectorOption(options); } +RTENTRY__ void RubberBandStretcher::setPhaseOption(Options options) { m_d->setPhaseOption(options); } +RTENTRY__ void RubberBandStretcher::setFormantOption(Options options) { m_d->setFormantOption(options); } +RTENTRY__ void RubberBandStretcher::setPitchOption(Options options) { @@ -126,6 +474,7 @@ RubberBandStretcher::setKeyFrameMap(const std::map &mapping) m_d->setKeyFrameMap(mapping); } +RTENTRY__ size_t RubberBandStretcher::getSamplesRequired() const { @@ -139,6 +488,7 @@ RubberBandStretcher::study(const float *const *input, size_t samples, m_d->study(input, samples, final); } +RTENTRY__ void RubberBandStretcher::process(const float *const *input, size_t samples, bool final) @@ -146,12 +496,14 @@ RubberBandStretcher::process(const float *const *input, size_t samples, m_d->process(input, samples, final); } +RTENTRY__ int RubberBandStretcher::available() const { return m_d->available(); } +RTENTRY__ size_t RubberBandStretcher::retrieve(float *const *output, size_t samples) const { @@ -194,6 +546,7 @@ RubberBandStretcher::getExactTimePoints() const return m_d->getExactTimePoints(); } +RTENTRY__ size_t RubberBandStretcher::getChannelCount() const { diff --git a/src/StretchCalculator.cpp b/src/StretchCalculator.cpp deleted file mode 100644 index 2e04de0..0000000 --- a/src/StretchCalculator.cpp +++ /dev/null @@ -1,1145 +0,0 @@ -/* -*- 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-2022 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 "StretchCalculator.h" - -#include -#include -#include -#include -#include -#include - -#include "system/sysutils.h" - -namespace RubberBand -{ - -StretchCalculator::StretchCalculator(size_t sampleRate, - size_t inputIncrement, - bool useHardPeaks) : - m_sampleRate(sampleRate), - m_increment(inputIncrement), - m_prevDf(0), - m_prevRatio(1.0), - m_prevTimeRatio(1.0), - m_transientAmnesty(0), - m_debugLevel(0), - m_useHardPeaks(useHardPeaks), - m_inFrameCounter(0), - m_frameCheckpoint(0, 0), - m_outFrameCounter(0) -{ -// std::cerr << "StretchCalculator::StretchCalculator: useHardPeaks = " << useHardPeaks << std::endl; -} - -StretchCalculator::~StretchCalculator() -{ -} - -void -StretchCalculator::setKeyFrameMap(const std::map &mapping) -{ - m_keyFrameMap = mapping; - - // Ensure we always have a 0 -> 0 mapping. If there's nothing in - // the map at all, don't need to worry about this (empty map is - // handled separately anyway) - if (!m_keyFrameMap.empty()) { - if (m_keyFrameMap.find(0) == m_keyFrameMap.end()) { - m_keyFrameMap[0] = 0; - } - } -} - -std::vector -StretchCalculator::calculate(double ratio, size_t inputDuration, - const std::vector &phaseResetDf, - const std::vector &stretchDf) -{ - assert(phaseResetDf.size() == stretchDf.size()); - - m_peaks = findPeaks(phaseResetDf); - - size_t totalCount = phaseResetDf.size(); - - size_t outputDuration = lrint(inputDuration * ratio); - - if (m_debugLevel > 0) { - std::cerr << "StretchCalculator::calculate(): inputDuration " << inputDuration << ", ratio " << ratio << ", outputDuration " << outputDuration; - } - - outputDuration = lrint((phaseResetDf.size() * m_increment) * ratio); - - if (m_debugLevel > 0) { - std::cerr << " (rounded up to " << outputDuration << ")"; - std::cerr << ", df size " << phaseResetDf.size() << ", increment " - << m_increment << std::endl; - } - - std::vector peaks; // peak position (in chunks) and hardness - std::vector targets; // targets for mapping peaks (in samples) - mapPeaks(peaks, targets, outputDuration, totalCount); - - if (m_debugLevel > 1) { - std::cerr << "have " << peaks.size() << " fixed positions" << std::endl; - } - - size_t totalInput = 0, totalOutput = 0; - - // For each region between two consecutive time sync points, we - // want to take the number of output chunks to be allocated and - // the detection function values within the range, and produce a - // series of increments that sum to the number of output chunks, - // such that each increment is displaced from the input increment - // by an amount inversely proportional to the magnitude of the - // stretch detection function at that input step. - - size_t regionTotalChunks = 0; - - std::vector increments; - - for (size_t i = 0; i <= peaks.size(); ++i) { - - size_t regionStart, regionStartChunk, regionEnd, regionEndChunk; - bool phaseReset = false; - - if (i == 0) { - regionStartChunk = 0; - regionStart = 0; - } else { - regionStartChunk = peaks[i-1].chunk; - regionStart = targets[i-1]; - phaseReset = peaks[i-1].hard; - } - - if (i == peaks.size()) { -// std::cerr << "note: i (=" << i << ") == peaks.size(); regionEndChunk " << regionEndChunk << " -> " << totalCount << ", regionEnd " << regionEnd << " -> " << outputDuration << std::endl; - regionEndChunk = totalCount; - regionEnd = outputDuration; - } else { - regionEndChunk = peaks[i].chunk; - regionEnd = targets[i]; - } - - if (regionStartChunk > totalCount) regionStartChunk = totalCount; - if (regionStart > outputDuration) regionStart = outputDuration; - if (regionEndChunk > totalCount) regionEndChunk = totalCount; - if (regionEnd > outputDuration) regionEnd = outputDuration; - - size_t regionDuration = regionEnd - regionStart; - regionTotalChunks += regionDuration; - - std::vector dfRegion; - - for (size_t j = regionStartChunk; j != regionEndChunk; ++j) { - dfRegion.push_back(stretchDf[j]); - } - - if (m_debugLevel > 1) { - std::cerr << "distributeRegion from " << regionStartChunk << " to " << regionEndChunk << " (samples " << regionStart << " to " << regionEnd << ")" << std::endl; - } - - dfRegion = smoothDF(dfRegion); - - std::vector regionIncrements = distributeRegion - (dfRegion, regionDuration, ratio, phaseReset); - - size_t totalForRegion = 0; - - for (size_t j = 0; j < regionIncrements.size(); ++j) { - - int incr = regionIncrements[j]; - - if (j == 0 && phaseReset) increments.push_back(-incr); - else increments.push_back(incr); - - if (incr > 0) totalForRegion += incr; - else totalForRegion += -incr; - - totalInput += m_increment; - } - - if (totalForRegion != regionDuration) { - std::cerr << "*** ERROR: distributeRegion returned wrong duration " << totalForRegion << ", expected " << regionDuration << std::endl; - } - - totalOutput += totalForRegion; - } - - if (m_debugLevel > 0) { - std::cerr << "total input increment = " << totalInput << " (= " << totalInput / m_increment << " chunks), output = " << totalOutput << ", ratio = " << double(totalOutput)/double(totalInput) << ", ideal output " << size_t(ceil(totalInput * ratio)) << std::endl; - std::cerr << "(region total = " << regionTotalChunks << ")" << std::endl; - } - - return increments; -} - -void -StretchCalculator::mapPeaks(std::vector &peaks, - std::vector &targets, - size_t outputDuration, - size_t totalCount) -{ - // outputDuration is in audio samples; totalCount is in chunks - - if (m_keyFrameMap.empty()) { - // "normal" behaviour -- fixed points are strictly in - // proportion - peaks = m_peaks; - for (size_t i = 0; i < peaks.size(); ++i) { - targets.push_back - (lrint((double(peaks[i].chunk) * outputDuration) / totalCount)); - } - return; - } - - // We have been given a set of source -> target sample frames in - // m_keyFrameMap. We want to ensure that (to the nearest chunk) these - // are followed exactly, and any fixed points that we calculated - // ourselves are interpolated in linear proportion in between. - - size_t peakidx = 0; - std::map::const_iterator mi = m_keyFrameMap.begin(); - - // NB we know for certain we have a mapping from 0 -> 0 (or at - // least, some mapping for source sample 0) because that is - // enforced in setKeyFrameMap above. However, we aren't guaranteed - // to have a mapping for the total duration -- we will usually - // need to assume it maps to the normal duration * ratio sample - - while (mi != m_keyFrameMap.end()) { - -// std::cerr << "mi->first is " << mi->first << ", second is " << mi->second <first / m_increment; - size_t sourceEndChunk = totalCount; - - size_t targetStartSample = mi->second; - size_t targetEndSample = outputDuration; - - ++mi; - if (mi != m_keyFrameMap.end()) { - sourceEndChunk = mi->first / m_increment; - targetEndSample = mi->second; - } - - if (sourceStartChunk >= totalCount || - sourceStartChunk >= sourceEndChunk || - targetStartSample >= outputDuration || - targetStartSample >= targetEndSample) { - std::cerr << "NOTE: ignoring mapping from chunk " << sourceStartChunk << " to sample " << targetStartSample << "\n(source or target chunk exceeds total count, or end is not later than start)" << std::endl; - continue; - } - - // one peak and target for the mapping, then one for each of - // the computed peaks that appear before the following mapping - - Peak p; - p.chunk = sourceStartChunk; - p.hard = false; // mappings are in time only, not phase reset points - peaks.push_back(p); - targets.push_back(targetStartSample); - - if (m_debugLevel > 1) { - std::cerr << "mapped chunk " << sourceStartChunk << " (frame " << sourceStartChunk * m_increment << ") -> " << targetStartSample << std::endl; - } - - while (peakidx < m_peaks.size()) { - - size_t pchunk = m_peaks[peakidx].chunk; - - if (pchunk < sourceStartChunk) { - // shouldn't happen, should have been dealt with - // already -- but no harm in ignoring it explicitly - ++peakidx; - continue; - } - if (pchunk == sourceStartChunk) { - // convert that last peak to a hard one, after all - peaks[peaks.size()-1].hard = true; - ++peakidx; - continue; - } - if (pchunk >= sourceEndChunk) { - // leave the rest for after the next mapping - break; - } - p.chunk = pchunk; - p.hard = m_peaks[peakidx].hard; - - double proportion = - double(pchunk - sourceStartChunk) / - double(sourceEndChunk - sourceStartChunk); - - size_t target = - targetStartSample + - lrint(proportion * - (targetEndSample - targetStartSample)); - - if (target <= targets[targets.size()-1] + m_increment) { - // peaks will become too close together afterwards, ignore - ++peakidx; - continue; - } - - if (m_debugLevel > 1) { - std::cerr << " peak chunk " << pchunk << " (frame " << pchunk * m_increment << ") -> " << target << std::endl; - } - - peaks.push_back(p); - targets.push_back(target); - ++peakidx; - } - } -} - -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 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 - // from the ratio directly. For the moment we're happy if it - // works well in common situations. - - float transientThreshold = 0.35f; -// if (ratio > 1) transientThreshold = 0.25f; - - if (m_useHardPeaks && df > m_prevDf * 1.1f && df > transientThreshold) { - 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) { - std::cerr << "df = " << df << ", prevDf = " << m_prevDf - << ", thresh = " << transientThreshold << std::endl; - } - - m_prevDf = df; - - 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; - } - - // as in offline mode, 0.05 sec approx min between transients - m_transientAmnesty = - lrint(ceil(double(m_sampleRate) / (20 * double(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 (m_debugLevel > 1) { - std::cerr << "StretchCalculator::calculateSingle: returning isTransient = " - << isTransient << ", outIncrement = " << outIncrement - << std::endl; - } - - m_inFrameCounter += inIncrement; - m_outFrameCounter += outIncrement * effectivePitchRatio; - - if (isTransient) { - return -outIncrement; - } else { - return outIncrement; - } -} - -void -StretchCalculator::reset() -{ - m_prevDf = 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(); -} - -std::vector -StretchCalculator::findPeaks(const std::vector &rawDf) -{ - std::vector df = smoothDF(rawDf); - - // We distinguish between "soft" and "hard" peaks. A soft peak is - // simply the result of peak-picking on the smoothed onset - // detection function, and it represents any (strong-ish) onset. - // We aim to ensure always that soft peaks are placed at the - // correct position in time. A hard peak is where there is a very - // rapid rise in detection function, and it presumably represents - // a more broadband, noisy transient. For these we perform a - // phase reset (if in the appropriate mode), and we locate the - // reset at the first point where we notice enough of a rapid - // rise, rather than necessarily at the peak itself, in order to - // preserve the shape of the transient. - - std::set hardPeakCandidates; - std::set softPeakCandidates; - - if (m_useHardPeaks) { - - // 0.05 sec approx min between hard peaks - size_t hardPeakAmnesty = lrint(ceil(double(m_sampleRate) / - (20 * double(m_increment)))); - size_t prevHardPeak = 0; - - if (m_debugLevel > 1) { - std::cerr << "hardPeakAmnesty = " << hardPeakAmnesty << std::endl; - } - - for (size_t i = 1; i + 1 < df.size(); ++i) { - - if (df[i] < 0.1) continue; - if (df[i] <= df[i-1] * 1.1) continue; - if (df[i] < 0.22) continue; - - if (!hardPeakCandidates.empty() && - i < prevHardPeak + hardPeakAmnesty) { - continue; - } - - bool hard = (df[i] > 0.4); - - if (hard && (m_debugLevel > 1)) { - std::cerr << "hard peak at " << i << ": " << df[i] - << " > absolute " << 0.4 - << std::endl; - } - - if (!hard) { - hard = (df[i] > df[i-1] * 1.4); - - if (hard && (m_debugLevel > 1)) { - std::cerr << "hard peak at " << i << ": " << df[i] - << " > prev " << df[i-1] << " * 1.4" - << std::endl; - } - } - - if (!hard && i > 1) { - hard = (df[i] > df[i-1] * 1.2 && - df[i-1] > df[i-2] * 1.2); - - if (hard && (m_debugLevel > 1)) { - std::cerr << "hard peak at " << i << ": " << df[i] - << " > prev " << df[i-1] << " * 1.2 and " - << df[i-1] << " > prev " << df[i-2] << " * 1.2" - << std::endl; - } - } - - if (!hard && i > 2) { - // have already established that df[i] > df[i-1] * 1.1 - hard = (df[i] > 0.3 && - df[i-1] > df[i-2] * 1.1 && - df[i-2] > df[i-3] * 1.1); - - if (hard && (m_debugLevel > 1)) { - std::cerr << "hard peak at " << i << ": " << df[i] - << " > prev " << df[i-1] << " * 1.1 and " - << df[i-1] << " > prev " << df[i-2] << " * 1.1 and " - << df[i-2] << " > prev " << df[i-3] << " * 1.1" - << std::endl; - } - } - - if (!hard) continue; - -// (df[i+1] > df[i] && df[i+1] > df[i-1] * 1.8) || -// df[i] > 0.4) { - - size_t peakLocation = i; - - if (i + 1 < rawDf.size() && - rawDf[i + 1] > rawDf[i] * 1.4) { - - ++peakLocation; - - if (m_debugLevel > 1) { - std::cerr << "pushing hard peak forward to " << peakLocation << ": " << df[peakLocation] << " > " << df[peakLocation-1] << " * " << 1.4 << std::endl; - } - } - - hardPeakCandidates.insert(peakLocation); - prevHardPeak = peakLocation; - } - } - - size_t medianmaxsize = lrint(ceil(double(m_sampleRate) / - double(m_increment))); // 1 sec ish - - if (m_debugLevel > 1) { - std::cerr << "mediansize = " << medianmaxsize << std::endl; - } - if (medianmaxsize < 7) { - medianmaxsize = 7; - if (m_debugLevel > 1) { - std::cerr << "adjusted mediansize = " << medianmaxsize << std::endl; - } - } - - int minspacing = lrint(ceil(double(m_sampleRate) / - (20 * double(m_increment)))); // 0.05 sec ish - - std::deque medianwin; - std::vector sorted; - int softPeakAmnesty = 0; - - for (size_t i = 0; i < medianmaxsize/2; ++i) { - medianwin.push_back(0); - } - for (size_t i = 0; i < medianmaxsize/2 && i < df.size(); ++i) { - medianwin.push_back(df[i]); - } - - size_t lastSoftPeak = 0; - - for (size_t i = 0; i < df.size(); ++i) { - - size_t mediansize = medianmaxsize; - - if (medianwin.size() < mediansize) { - mediansize = medianwin.size(); - } - - size_t middle = medianmaxsize / 2; - if (middle >= mediansize) middle = mediansize-1; - - size_t nextDf = i + mediansize - middle; - - if (mediansize < 2) { - if (mediansize > medianmaxsize) { // absurd, but never mind that - medianwin.pop_front(); - } - if (nextDf < df.size()) { - medianwin.push_back(df[nextDf]); - } else { - medianwin.push_back(0); - } - continue; - } - - if (m_debugLevel > 2) { -// std::cerr << "have " << mediansize << " in median buffer" << std::endl; - } - - sorted.clear(); - for (size_t j = 0; j < mediansize; ++j) { - sorted.push_back(medianwin[j]); - } - std::sort(sorted.begin(), sorted.end()); - - size_t n = 90; // percentile above which we pick peaks - size_t index = (sorted.size() * n) / 100; - if (index >= sorted.size()) index = sorted.size()-1; - if (index == sorted.size()-1 && index > 0) --index; - float thresh = sorted[index]; - -// if (m_debugLevel > 2) { -// std::cerr << "medianwin[" << middle << "] = " << medianwin[middle] << ", thresh = " << thresh << std::endl; -// if (medianwin[middle] == 0.f) { -// std::cerr << "contents: "; -// for (size_t j = 0; j < medianwin.size(); ++j) { -// std::cerr << medianwin[j] << " "; -// } -// std::cerr << std::endl; -// } -// } - - if (medianwin[middle] > thresh && - medianwin[middle] > medianwin[middle-1] && - medianwin[middle] > medianwin[middle+1] && - softPeakAmnesty == 0) { - - size_t maxindex = middle; - float maxval = medianwin[middle]; - - for (size_t j = middle+1; j < mediansize; ++j) { - if (medianwin[j] > maxval) { - maxval = medianwin[j]; - maxindex = j; - } else if (medianwin[j] < medianwin[middle]) { - break; - } - } - - size_t peak = i + maxindex - middle; - -// std::cerr << "i = " << i << ", maxindex = " << maxindex << ", middle = " << middle << ", so peak at " << peak << std::endl; - - if (softPeakCandidates.empty() || lastSoftPeak != peak) { - - if (m_debugLevel > 1) { - std::cerr << "soft peak at " << peak << " (" - << peak * m_increment << "): " - << medianwin[middle] << " > " - << thresh << " and " - << medianwin[middle] - << " > " << medianwin[middle-1] << " and " - << medianwin[middle] - << " > " << medianwin[middle+1] - << std::endl; - } - - if (peak >= df.size()) { - if (m_debugLevel > 2) { - std::cerr << "peak is beyond end" << std::endl; - } - } else { - softPeakCandidates.insert(peak); - lastSoftPeak = peak; - } - } - - softPeakAmnesty = minspacing + maxindex - middle; - if (m_debugLevel > 2) { - std::cerr << "amnesty = " << softPeakAmnesty << std::endl; - } - - } else if (softPeakAmnesty > 0) --softPeakAmnesty; - - if (mediansize >= medianmaxsize) { - medianwin.pop_front(); - } - if (nextDf < df.size()) { - medianwin.push_back(df[nextDf]); - } else { - medianwin.push_back(0); - } - } - - std::vector peaks; - - while (!hardPeakCandidates.empty() || !softPeakCandidates.empty()) { - - bool haveHardPeak = !hardPeakCandidates.empty(); - bool haveSoftPeak = !softPeakCandidates.empty(); - - size_t hardPeak = (haveHardPeak ? *hardPeakCandidates.begin() : 0); - size_t softPeak = (haveSoftPeak ? *softPeakCandidates.begin() : 0); - - Peak peak; - peak.hard = false; - peak.chunk = softPeak; - - bool ignore = false; - - if (haveHardPeak && - (!haveSoftPeak || hardPeak <= softPeak)) { - - if (m_debugLevel > 2) { - std::cerr << "Hard peak: " << hardPeak << std::endl; - } - - peak.hard = true; - peak.chunk = hardPeak; - hardPeakCandidates.erase(hardPeakCandidates.begin()); - - } else { - if (m_debugLevel > 2) { - std::cerr << "Soft peak: " << softPeak << std::endl; - } - if (!peaks.empty() && - peaks[peaks.size()-1].hard && - peaks[peaks.size()-1].chunk + 3 >= softPeak) { - if (m_debugLevel > 2) { - std::cerr << "(ignoring, as we just had a hard peak)" - << std::endl; - } - ignore = true; - } - } - - if (haveSoftPeak && peak.chunk == softPeak) { - softPeakCandidates.erase(softPeakCandidates.begin()); - } - - if (!ignore) { - peaks.push_back(peak); - } - } - - return peaks; -} - -std::vector -StretchCalculator::smoothDF(const std::vector &df) -{ - std::vector smoothedDF; - - for (size_t i = 0; i < df.size(); ++i) { - // three-value moving mean window for simple smoothing - float total = 0.f, count = 0; - if (i > 0) { total += df[i-1]; ++count; } - total += df[i]; ++count; - if (i+1 < df.size()) { total += df[i+1]; ++count; } - float mean = total / count; - smoothedDF.push_back(mean); - } - - return smoothedDF; -} - -std::vector -StretchCalculator::distributeRegion(const std::vector &dfIn, - size_t duration, float ratio, bool phaseReset) -{ - std::vector df(dfIn); - std::vector increments; - - // The peak for the stretch detection function may appear after - // the peak that we're using to calculate the start of the region. - // We don't want that. If we find a peak in the first half of - // the region, we should set all the values up to that point to - // the same value as the peak. - - // (This might not be subtle enough, especially if the region is - // long -- we want a bound that corresponds to acoustic perception - // of the audible bounce.) - - for (size_t i = 1; i < df.size()/2; ++i) { - if (df[i] < df[i-1]) { - if (m_debugLevel > 1) { - std::cerr << "stretch peak offset: " << i-1 << " (peak " << df[i-1] << ")" << std::endl; - } - for (size_t j = 0; j < i-1; ++j) { - df[j] = df[i-1]; - } - break; - } - } - - float maxDf = 0; - - for (size_t i = 0; i < df.size(); ++i) { - if (i == 0 || df[i] > maxDf) maxDf = df[i]; - } - - // We want to try to ensure the last 100ms or so (if possible) are - // tending back towards the maximum df, so that the stretchiness - // reduces at the end of the stretched region. - - int reducedRegion = lrint((0.1 * m_sampleRate) / m_increment); - if (reducedRegion > int(df.size()/5)) reducedRegion = df.size()/5; - - for (int i = 0; i < reducedRegion; ++i) { - size_t index = df.size() - reducedRegion + i; - df[index] = df[index] + ((maxDf - df[index]) * i) / reducedRegion; - } - - long toAllot = long(duration) - long(m_increment * df.size()); - - if (m_debugLevel > 1) { - std::cerr << "region of " << df.size() << " chunks, output duration " << duration << ", increment " << m_increment << ", toAllot " << toAllot << std::endl; - } - - size_t totalIncrement = 0; - - // We place limits on the amount of displacement per chunk. if - // ratio < 0, no increment should be larger than increment*ratio - // or smaller than increment*ratio/2; if ratio > 0, none should be - // smaller than increment*ratio or larger than increment*ratio*2. - // We need to enforce this in the assignment of displacements to - // allotments, not by trying to respond if something turns out - // wrong. - - // Note that the ratio is only provided to this function for the - // purposes of establishing this bound to the displacement. - - // so if - // maxDisplacement / totalDisplacement > increment * ratio*2 - increment - // (for ratio > 1) - // or - // maxDisplacement / totalDisplacement < increment * ratio/2 - // (for ratio < 1) - - // then we need to adjust and accommodate - - double totalDisplacement = 0; - double maxDisplacement = 0; // min displacement will be 0 by definition - - maxDf = 0; - float adj = 0; - - bool tooShort = true, tooLong = true; - const int acceptableIterations = 10; - int iteration = 0; - int prevExtreme = 0; - bool better = false; - - while ((tooLong || tooShort) && iteration < acceptableIterations) { - - ++iteration; - - tooLong = false; - tooShort = false; - calculateDisplacements(df, maxDf, totalDisplacement, maxDisplacement, - adj); - - if (m_debugLevel > 1) { - std::cerr << "totalDisplacement " << totalDisplacement << ", max " << maxDisplacement << " (maxDf " << maxDf << ", df count " << df.size() << ")" << std::endl; - } - - if (totalDisplacement == 0) { -// Not usually a problem, in fact -// std::cerr << "WARNING: totalDisplacement == 0 (duration " << duration << ", " << df.size() << " values in df)" << std::endl; - if (!df.empty() && adj == 0) { - tooLong = true; tooShort = true; - adj = 1; - } - continue; - } - - int extremeIncrement = m_increment + - lrint((toAllot * maxDisplacement) / totalDisplacement); - - if (extremeIncrement < 0) { - if (m_debugLevel > 0) { - std::cerr << "NOTE: extreme increment " << extremeIncrement << " < 0, adjusting" << std::endl; - } - tooShort = true; - } else { - if (ratio < 1.0) { - if (extremeIncrement > lrint(ceil(m_increment * ratio))) { - std::cerr << "WARNING: extreme increment " - << extremeIncrement << " > " - << m_increment * ratio << std::endl; - } else if (extremeIncrement < (m_increment * ratio) / 2) { - if (m_debugLevel > 0) { - std::cerr << "NOTE: extreme increment " - << extremeIncrement << " < " - << (m_increment * ratio) / 2 - << ", adjusting" << std::endl; - } - tooShort = true; - if (iteration > 0) { - better = (extremeIncrement > prevExtreme); - } - prevExtreme = extremeIncrement; - } - } else { - if (extremeIncrement > m_increment * ratio * 2) { - if (m_debugLevel > 0) { - std::cerr << "NOTE: extreme increment " - << extremeIncrement << " > " - << m_increment * ratio * 2 - << ", adjusting" << std::endl; - } - tooLong = true; - if (iteration > 0) { - better = (extremeIncrement < prevExtreme); - } - prevExtreme = extremeIncrement; - } else if (extremeIncrement < lrint(floor(m_increment * ratio))) { - std::cerr << "WARNING: extreme increment " - << extremeIncrement << " < " - << m_increment * ratio << std::endl; - } - } - } - - if (tooLong || tooShort) { - // Need to make maxDisplacement smaller as a proportion of - // the total displacement, yet ensure that the - // displacements still sum to the total. - adj += maxDf/10; - } - } - - if (tooLong) { - if (better) { - // we were iterating in the right direction, so - // leave things as they are (and undo that last tweak) - std::cerr << "WARNING: No acceptable displacement adjustment found, using latest values:\nthis region could sound bad" << std::endl; - adj -= maxDf/10; - } else { - std::cerr << "WARNING: No acceptable displacement adjustment found, using defaults:\nthis region could sound bad" << std::endl; - adj = 1; - calculateDisplacements(df, maxDf, totalDisplacement, maxDisplacement, - adj); - } - } else if (tooShort) { - std::cerr << "WARNING: No acceptable displacement adjustment found, using flat distribution:\nthis region could sound bad" << std::endl; - adj = 1; - for (size_t i = 0; i < df.size(); ++i) { - df[i] = 1.f; - } - calculateDisplacements(df, maxDf, totalDisplacement, maxDisplacement, - adj); - } - - for (size_t i = 0; i < df.size(); ++i) { - - double displacement = maxDf - df[i]; - if (displacement < 0) displacement -= adj; - else displacement += adj; - - if (i == 0 && phaseReset) { - if (m_debugLevel > 2) { - std::cerr << "Phase reset at first chunk" << std::endl; - } - if (df.size() == 1) { - increments.push_back(duration); - totalIncrement += duration; - } else { - increments.push_back(m_increment); - totalIncrement += m_increment; - } - totalDisplacement -= displacement; - continue; - } - - double theoreticalAllotment = 0; - - if (totalDisplacement != 0) { - theoreticalAllotment = (toAllot * displacement) / totalDisplacement; - } - int allotment = lrint(theoreticalAllotment); - if (i + 1 == df.size()) allotment = toAllot; - - int increment = m_increment + allotment; - - if (increment < 0) { - // this is a serious problem, the allocation is quite - // wrong if it allows increment to diverge so far from the - // input increment (though it can happen legitimately if - // asked to squash very violently) - std::cerr << "*** WARNING: increment " << increment << " <= 0, rounding to zero" << std::endl; - - toAllot += m_increment; - increment = 0; - - } else { - toAllot -= allotment; - } - - increments.push_back(increment); - totalIncrement += increment; - - totalDisplacement -= displacement; - - if (m_debugLevel > 2) { - std::cerr << "df " << df[i] << ", smoothed " << df[i] << ", disp " << displacement << ", allot " << theoreticalAllotment << ", incr " << increment << ", remain " << toAllot << std::endl; - } - } - - if (m_debugLevel > 2) { - std::cerr << "total increment: " << totalIncrement << ", left over: " << toAllot << " to allot, displacement " << totalDisplacement << std::endl; - } - - if (totalIncrement != duration) { - std::cerr << "*** WARNING: calculated output duration " << totalIncrement << " != expected " << duration << std::endl; - } - - return increments; -} - -void -StretchCalculator::calculateDisplacements(const std::vector &df, - float &maxDf, - double &totalDisplacement, - double &maxDisplacement, - float adj) const -{ - totalDisplacement = maxDisplacement = 0; - - maxDf = 0; - - for (size_t i = 0; i < df.size(); ++i) { - if (i == 0 || df[i] > maxDf) maxDf = df[i]; - } - - for (size_t i = 0; i < df.size(); ++i) { - double displacement = maxDf - df[i]; - if (displacement < 0) displacement -= adj; - else displacement += adj; - totalDisplacement += displacement; - if (i == 0 || displacement > maxDisplacement) { - maxDisplacement = displacement; - } - } -} - -} - diff --git a/src/audiocurves/SpectralDifferenceAudioCurve.cpp b/src/audiocurves/SpectralDifferenceAudioCurve.cpp deleted file mode 100644 index ee3b85b..0000000 --- a/src/audiocurves/SpectralDifferenceAudioCurve.cpp +++ /dev/null @@ -1,107 +0,0 @@ -/* -*- 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-2022 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 "SpectralDifferenceAudioCurve.h" - -#include "../system/Allocators.h" -#include "../system/VectorOps.h" - -namespace RubberBand -{ - - -SpectralDifferenceAudioCurve::SpectralDifferenceAudioCurve(Parameters parameters) : - AudioCurveCalculator(parameters) -{ - m_mag = allocate(m_lastPerceivedBin + 1); - m_tmpbuf = allocate(m_lastPerceivedBin + 1); - v_zero(m_mag, m_lastPerceivedBin + 1); -} - -SpectralDifferenceAudioCurve::~SpectralDifferenceAudioCurve() -{ - deallocate(m_mag); - deallocate(m_tmpbuf); -} - -void -SpectralDifferenceAudioCurve::reset() -{ - v_zero(m_mag, m_lastPerceivedBin + 1); -} - -void -SpectralDifferenceAudioCurve::setFftSize(int newSize) -{ - deallocate(m_tmpbuf); - deallocate(m_mag); - AudioCurveCalculator::setFftSize(newSize); - m_mag = allocate(m_lastPerceivedBin + 1); - m_tmpbuf = allocate(m_lastPerceivedBin + 1); - reset(); -} - -float -SpectralDifferenceAudioCurve::processFloat(const float *R__ mag, int) -{ - double result = 0.0; - - const int hs1 = m_lastPerceivedBin + 1; - - v_convert(m_tmpbuf, mag, hs1); - v_square(m_tmpbuf, hs1); - v_subtract(m_mag, m_tmpbuf, hs1); - v_abs(m_mag, hs1); - v_sqrt(m_mag, hs1); - - for (int i = 0; i < hs1; ++i) { - result += m_mag[i]; - } - - v_copy(m_mag, m_tmpbuf, hs1); - return result; -} - -double -SpectralDifferenceAudioCurve::processDouble(const double *R__ mag, int) -{ - double result = 0.0; - - const int hs1 = m_lastPerceivedBin + 1; - - v_convert(m_tmpbuf, mag, hs1); - v_square(m_tmpbuf, hs1); - v_subtract(m_mag, m_tmpbuf, hs1); - v_abs(m_mag, hs1); - v_sqrt(m_mag, hs1); - - for (int i = 0; i < hs1; ++i) { - result += m_mag[i]; - } - - v_copy(m_mag, m_tmpbuf, hs1); - return result; -} - -} - diff --git a/src/system/Allocators.cpp b/src/common/Allocators.cpp similarity index 100% rename from src/system/Allocators.cpp rename to src/common/Allocators.cpp diff --git a/src/system/Allocators.h b/src/common/Allocators.h similarity index 100% rename from src/system/Allocators.h rename to src/common/Allocators.h diff --git a/src/dsp/BQResampler.cpp b/src/common/BQResampler.cpp similarity index 99% rename from src/dsp/BQResampler.cpp rename to src/common/BQResampler.cpp index f426013..2ac5f50 100644 --- a/src/dsp/BQResampler.cpp +++ b/src/common/BQResampler.cpp @@ -28,8 +28,8 @@ #include #include -#include "../system/Allocators.h" -#include "../system/VectorOps.h" +#include "Allocators.h" +#include "VectorOps.h" #define BQ_R__ R__ diff --git a/src/dsp/BQResampler.h b/src/common/BQResampler.h similarity index 98% rename from src/dsp/BQResampler.h rename to src/common/BQResampler.h index 3b11203..4858096 100644 --- a/src/dsp/BQResampler.h +++ b/src/common/BQResampler.h @@ -26,8 +26,8 @@ #include -#include "../system/Allocators.h" -#include "../system/VectorOps.h" +#include "Allocators.h" +#include "VectorOps.h" namespace RubberBand { diff --git a/src/dsp/FFT.cpp b/src/common/FFT.cpp similarity index 99% rename from src/dsp/FFT.cpp rename to src/common/FFT.cpp index 2ae40de..984901f 100644 --- a/src/dsp/FFT.cpp +++ b/src/common/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 "Thread.h" +#include "Profiler.h" +#include "Allocators.h" +#include "VectorOps.h" +#include "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/common/FFT.h similarity index 99% rename from src/dsp/FFT.h rename to src/common/FFT.h index 73b094e..54c8ad0 100644 --- a/src/dsp/FFT.h +++ b/src/common/FFT.h @@ -24,7 +24,7 @@ #ifndef RUBBERBAND_FFT_H #define RUBBERBAND_FFT_H -#include "../system/sysutils.h" +#include "sysutils.h" #include #include diff --git a/src/audiocurves/ConstantAudioCurve.h b/src/common/FixedVector.h similarity index 69% rename from src/audiocurves/ConstantAudioCurve.h rename to src/common/FixedVector.h index b1a8e50..50cabb8 100644 --- a/src/audiocurves/ConstantAudioCurve.h +++ b/src/common/FixedVector.h @@ -21,23 +21,26 @@ you must obtain a valid commercial licence before doing so. */ -#ifndef RUBBERBAND_CONSTANT_AUDIO_CURVE_H -#define RUBBERBAND_CONSTANT_AUDIO_CURVE_H +#ifndef RUBBERBAND_FIXED_VECTOR_H +#define RUBBERBAND_FIXED_VECTOR_H -#include "../dsp/AudioCurveCalculator.h" +#include "Allocators.h" +#include namespace RubberBand { -class ConstantAudioCurve : public AudioCurveCalculator +template +class FixedVector : public std::vector> { public: - ConstantAudioCurve(Parameters parameters); - virtual ~ConstantAudioCurve(); - - virtual float processFloat(const float *R__ mag, int increment); - virtual double processDouble(const double *R__ mag, int increment); - virtual void reset(); + using std::vector>::vector; + +private: + FixedVector(const FixedVector &) =delete; + FixedVector(FixedVector &&) =delete; + FixedVector &operator=(const FixedVector &) =delete; + FixedVector &operator=(FixedVector &&) =delete; }; } diff --git a/src/common/HistogramFilter.h b/src/common/HistogramFilter.h new file mode 100644 index 0000000..b14331e --- /dev/null +++ b/src/common/HistogramFilter.h @@ -0,0 +1,194 @@ +/* -*- 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-2022 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 RUBBERBAND_HISTOGRAM_FILTER_H +#define RUBBERBAND_HISTOGRAM_FILTER_H + +#include "SingleThreadRingBuffer.h" + +#include +#include + +namespace RubberBand { + +/** + * A median or modal filter implemented using a histogram. The values + * must come from a compact set of integers within [0,n) where n is + * specified in the constructor. Pushing a value updates the + * histogram, after which you can get either the median or the mode. + * You can call drop() to drop the oldest value without pushing a new + * one, for example to drain the filter at the tail of the sequence. + */ +class HistogramFilter +{ +public: + HistogramFilter(int nValues, int filterLength) : + m_buffer(filterLength), + m_histogram(nValues, 0), + m_mode(-1) { } + ~HistogramFilter() { } + + int getFilterLength() const { + return m_buffer.getSize(); + } + + int getNValues() const { + return int(m_histogram.size()); + } + + void reset() { + m_buffer.reset(); + for (int i = 0; i < getNValues(); ++i) { + m_histogram[i] = 0; + } + } + + void push(int value) { + if (m_buffer.getWriteSpace() == 0) { + int toDrop = m_buffer.readOne(); + --m_histogram[toDrop]; + } + m_buffer.writeOne(value); + int height = ++m_histogram[value]; + if (m_mode >= 0 && height >= m_histogram[m_mode]) { + if (height > m_histogram[m_mode] || value < m_mode) { + m_mode = value; + } + } + } + + void drop() { + if (m_buffer.getReadSpace() > 0) { + int toDrop = m_buffer.readOne(); + --m_histogram[toDrop]; + if (toDrop == m_mode) { + m_mode = -1; + } + } + } + + /** Return the median of the values in the filter currently. If + * the median lies between two values, return the first of them. + */ + int getMedian() const { + int half = (m_buffer.getReadSpace() + 1) / 2; + int acc = 0; + int nvalues = getNValues(); + for (int i = 0; i < nvalues; ++i) { + acc += m_histogram[i]; + if (acc >= half) { + return i; + } + } + return 0; + } + + /** Return the modal value, that is, the value that occurs the + * most often in the filter currently. If multiple values occur + * an equal number of times, return the smallest of them. + */ + int getMode() const { + if (m_mode >= 0) { + return m_mode; + } + int max = 0; + int mode = 0; + int nvalues = getNValues(); + for (int i = 0; i < nvalues; ++i) { + int h = m_histogram[i]; + if (i == 0 || h > max) { + max = h; + mode = i; + } + } + m_mode = mode; + return mode; + } + + // Convenience function that filters an array in-place. Filter is + // a median filter unless modal is true. Array has length n. + // Modifies both the filter and the array. + // + static void filter(HistogramFilter &f, int *v, int n, bool modal = false) { + f.reset(); + int flen = f.getFilterLength(); + int i = -(flen / 2); + int j = 0; + while (i != n) { + if (j < n) { + f.push(v[j]); + } else if (j >= flen) { + f.drop(); + } + if (i >= 0) { + int m = modal ? f.getMode() : f.getMedian(); + v[i] = m; + } + ++i; + ++j; + } + } + + // As above but with a vector argument + // + static void filter(HistogramFilter &f, std::vector &v, bool modal = false) { + filter(f, v.data(), v.size(), modal); + } + + // Convenience function that median-filters an array + // in-place. Array has length n. Modifies both the filter and the + // array. + // + static void medianFilter(HistogramFilter &f, int *v, int n) { + filter(f, v, n, false); + } + + // As above but with a vector argument + // + static void medianFilter(HistogramFilter &f, std::vector &v) { + medianFilter(f, v.data(), v.size()); + } + + // Convenience function that modal-filters an array + // in-place. Array has length n. Modifies both the filter and the + // array. + // + static void modalFilter(HistogramFilter &f, int *v, int n) { + filter(f, v, n, true); + } + + // As above but with a vector argument + // + static void modalFilter(HistogramFilter &f, std::vector &v) { + modalFilter(f, v.data(), v.size()); + } + +private: + SingleThreadRingBuffer m_buffer; + std::vector m_histogram; + mutable int m_mode; +}; + +} + +#endif diff --git a/src/audiocurves/ConstantAudioCurve.cpp b/src/common/Log.cpp similarity index 71% rename from src/audiocurves/ConstantAudioCurve.cpp rename to src/common/Log.cpp index 0c2e08a..03b3536 100644 --- a/src/audiocurves/ConstantAudioCurve.cpp +++ b/src/common/Log.cpp @@ -21,37 +21,11 @@ you must obtain a valid commercial licence before doing so. */ -#include "ConstantAudioCurve.h" +#include "Log.h" namespace RubberBand { - -ConstantAudioCurve::ConstantAudioCurve(Parameters parameters) : - AudioCurveCalculator(parameters) -{ -} - -ConstantAudioCurve::~ConstantAudioCurve() -{ -} - -void -ConstantAudioCurve::reset() -{ -} - -float -ConstantAudioCurve::processFloat(const float *R__, int) -{ - return 1.f; -} - -double -ConstantAudioCurve::processDouble(const double *R__, int) -{ - return 1.0; -} +int Log::m_defaultDebugLevel = 0; } - diff --git a/src/common/Log.h b/src/common/Log.h new file mode 100644 index 0000000..b78315e --- /dev/null +++ b/src/common/Log.h @@ -0,0 +1,72 @@ +/* -*- 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-2022 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 RUBBERBAND_LOG_H +#define RUBBERBAND_LOG_H + +#include +#include + +namespace RubberBand { + +class Log { +public: + Log(std::function _log0, + std::function _log1, + std::function _log2) : + m_log0(_log0), + m_log1(_log1), + m_log2(_log2), + m_debugLevel(m_defaultDebugLevel) { } + + Log(const Log &other) =default; + Log(Log &&other) =default; + Log &operator=(const Log &other) =default; + Log &operator=(Log &&other) =default; + + void setDebugLevel(int level) { m_debugLevel = level; } + int getDebugLevel() const { return m_debugLevel; } + + static void setDefaultDebugLevel(int level) { m_defaultDebugLevel = level; } + + void log(int level, const char *message) const { + if (level <= m_debugLevel) m_log0(message); + } + void log(int level, const char *message, double arg0) const { + if (level <= m_debugLevel) m_log1(message, arg0); + } + void log(int level, const char *message, double arg0, double arg1) const { + if (level <= m_debugLevel) m_log2(message, arg0, arg1); + } + +private: + std::function m_log0; + std::function m_log1; + std::function m_log2; + int m_debugLevel; + static int m_defaultDebugLevel; +}; + +} + +#endif diff --git a/src/common/MovingMedian.h b/src/common/MovingMedian.h new file mode 100644 index 0000000..e7ede71 --- /dev/null +++ b/src/common/MovingMedian.h @@ -0,0 +1,256 @@ +/* -*- 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-2022 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 RUBBERBAND_MOVING_MEDIAN_H +#define RUBBERBAND_MOVING_MEDIAN_H + +#include "SampleFilter.h" +#include "FixedVector.h" +#include "Allocators.h" +#include "SingleThreadRingBuffer.h" + +#include +#include + +namespace RubberBand +{ + +/** + * A median or modal filter implemented using sorting, for continuous + * values. Pushing a value updates the sorted record, after which you + * can get() the median. You can call drop() to drop the oldest value + * without pushing a new one, for example to drain the filter at the + * tail of the sequence. + */ +template +class MovingMedian : public SampleFilter +{ + static constexpr float fifty = 50.f; + +public: + MovingMedian(int filterLength, float percentile = fifty) : + m_buffer(filterLength), + m_sortspace(filterLength, {}), + m_fill(0), + m_percentile(percentile) + { } + + ~MovingMedian() { } + + MovingMedian(const MovingMedian &) =default; + MovingMedian &operator=(const MovingMedian &) =default; + + int getSize() const { + return m_buffer.getSize(); + } + + void setPercentile(float p) { + m_percentile = p; + } + + void push(T value) { + if (value != value) { + std::cerr << "WARNING: MovingMedian: NaN encountered" << std::endl; + value = T(); + } + if (m_fill == getSize()) { + T toDrop = m_buffer.readOne(); + dropAndPut(toDrop, value); + } else { + put(value); + } + m_buffer.writeOne(value); + } + + void drop() { + if (m_fill > 0) { + T toDrop = m_buffer.readOne(); + drop(toDrop); + } + } + + /** Return the median of the values in the filter currently. If + * the median lies between two values, return the first of them. + */ + T get() const { + int n = m_fill - 1; + if (m_percentile == fifty) { // exact default value + return m_sortspace[n / 2]; + } else { + int index = int(floorf(float(n) * m_percentile / 100.f)); + if (index >= m_fill) index = m_fill - 1; + return m_sortspace[index]; + } + } + + void reset() { + m_buffer.reset(); + v_zero(m_sortspace.data(), m_sortspace.size()); + m_fill = 0; + } + + // Convenience function that applies a given filter to an array + // in-place. Array has length n. Modifies both the filter and the + // array. + // + static void filter(MovingMedian &f, T *v, int n) { + f.reset(); + int flen = f.getSize(); + int i = -(flen / 2); + int j = 0; + while (i != n) { + if (j < n) { + f.push(v[j]); + } else if (j >= flen) { + f.drop(); + } + if (i >= 0) { + v[i] = f.get(); + } + ++i; + ++j; + } + } + + // As above but with a vector argument + // + static void filter(MovingMedian &mm, std::vector &v) { + filter(mm, v.data(), v.size()); + } + +private: + SingleThreadRingBuffer m_buffer; + std::vector m_sortspace; + int m_fill; + float m_percentile; + + void dropAndPut(const T &toDrop, const T &toPut) { + // precondition: sorted contains getSize values, one of which is toDrop + // postcondition: sorted contains getSize values, one of which is toPut + // (and one instance of toDrop has been removed) + + // This implementation was timed for rather short filters (no + // longer than maybe 16 items). Two binary searches plus a + // memmove should be faster for longer ones. + + const int n = m_fill; + T *sorted = m_sortspace.data(); + int dropIx; + if (toDrop <= *sorted) { + // this is quite a common short-circuit in situations + // where many values can be (the equivalent of) 0 + dropIx = 0; + } else { + dropIx = std::lower_bound(sorted, sorted + n, toDrop) - sorted; + } + if (toPut > toDrop) { + int i = dropIx; + while (i+1 < n) { + if (sorted[i+1] > toPut) { + break; + } + sorted[i] = sorted[i+1]; + ++i; + } + sorted[i] = toPut; + } else if (toPut < toDrop) { + int i = dropIx; + while (true) { + if (--i < 0 || sorted[i] < toPut) { + break; + } + sorted[i+1] = sorted[i]; + } + sorted[i+1] = toPut; + } + } + + void put(const T &toPut) { + // precondition: sorted contains m_fill values, m_fill < m_length, + // packed at the start + // postcondition: m_fill is incremented, sorted contains m_fill values, + // packed at the start, one of which is toPut + int n = m_fill; + T *sorted = m_sortspace.data(); + int putIx = std::lower_bound(sorted, sorted + n, toPut) - sorted; + if (putIx < n) { + v_move(sorted + putIx + 1, sorted + putIx, n - putIx); + } + sorted[putIx] = toPut; + ++m_fill; + } + + void drop(const T &toDrop) { + // precondition: sorted contains m_fill values, m_fill > 0, <= m_length, + // packed at the start, one of which is toDrop + // postcondition: m_fill decremented, sorted contains m_fill values, + // packed at the start, none of which is toDrop + int n = m_fill; + T *sorted = m_sortspace.data(); + int dropIx = std::lower_bound(sorted, sorted + n, toDrop) - sorted; + if (dropIx < n - 1) { + v_move(sorted + dropIx, sorted + dropIx + 1, n - dropIx - 1); + } + --m_fill; + } + +}; + +template +class MovingMedianStack +{ +public: + MovingMedianStack(int nfilters, int size) : + m_stack(nfilters, { size }) + { + } + + ~MovingMedianStack() { + } + + int getSize() const { + return m_stack[0].getSize(); + } + + void push(int filter, T value) { + m_stack[filter].push(value); + } + + T get(int filter) const { + return m_stack[filter].get(); + } + + void reset() { + for (auto &f: m_stack) { + f.reset(); + } + } + +private: + std::vector> m_stack; +}; + +} + +#endif + diff --git a/src/base/Profiler.cpp b/src/common/Profiler.cpp similarity index 99% rename from src/base/Profiler.cpp rename to src/common/Profiler.cpp index 3d32e3e..a606cfa 100644 --- a/src/base/Profiler.cpp +++ b/src/common/Profiler.cpp @@ -23,7 +23,7 @@ #include "Profiler.h" -#include "../system/Thread.h" +#include "Thread.h" #include #include diff --git a/src/base/Profiler.h b/src/common/Profiler.h similarity index 98% rename from src/base/Profiler.h rename to src/common/Profiler.h index e9e3f56..37ec04c 100644 --- a/src/base/Profiler.h +++ b/src/common/Profiler.h @@ -42,7 +42,7 @@ #ifdef PROFILE_CLOCKS #include #else -#include "../system/sysutils.h" +#include "sysutils.h" #ifndef _WIN32 #include #endif diff --git a/src/dsp/Resampler.cpp b/src/common/Resampler.cpp similarity index 99% rename from src/dsp/Resampler.cpp rename to src/common/Resampler.cpp index 155daaa..32444a7 100644 --- a/src/dsp/Resampler.cpp +++ b/src/common/Resampler.cpp @@ -23,8 +23,8 @@ #include "Resampler.h" -#include "../system/Allocators.h" -#include "../system/VectorOps.h" +#include "Allocators.h" +#include "VectorOps.h" #include #include @@ -54,7 +54,7 @@ #endif #ifdef USE_SPEEX -#include "../speex/speex_resampler.h" +#include "../ext/speex/speex_resampler.h" #endif #ifdef USE_BQRESAMPLER diff --git a/src/dsp/Resampler.h b/src/common/Resampler.h similarity index 98% rename from src/dsp/Resampler.h rename to src/common/Resampler.h index 94abef3..6080e0f 100644 --- a/src/dsp/Resampler.h +++ b/src/common/Resampler.h @@ -24,7 +24,7 @@ #ifndef RUBBERBAND_RESAMPLER_H #define RUBBERBAND_RESAMPLER_H -#include "../system/sysutils.h" +#include "sysutils.h" namespace RubberBand { @@ -172,6 +172,9 @@ public: protected: Impl *d; int m_method; + + Resampler(const Resampler &) =delete; + Resampler &operator=(const Resampler &) =delete; }; } diff --git a/src/base/RingBuffer.h b/src/common/RingBuffer.h similarity index 95% rename from src/base/RingBuffer.h rename to src/common/RingBuffer.h index 9bb91e0..2fabd14 100644 --- a/src/base/RingBuffer.h +++ b/src/common/RingBuffer.h @@ -28,8 +28,8 @@ //#define DEBUG_RINGBUFFER 1 -#include "../system/sysutils.h" -#include "../system/Allocators.h" +#include "sysutils.h" +#include "Allocators.h" #include @@ -44,7 +44,6 @@ namespace RubberBand { * RingBuffer is thread-safe provided only one thread writes and only * one thread reads. */ - template class RingBuffer { @@ -138,8 +137,13 @@ public: * necessary to empty the buffer. If fewer than n are available, * the remainder will be zeroed out. Returns the number of * samples actually read. + * + * This is a template function, taking an argument S for the target + * sample type, which is permitted to differ from T if the two + * types are compatible for arithmetic operations. */ - int peek(T *const R__ destination, int n) const; + template + int peek(S *const R__ destination, int n) const; /** * Read one sample from the buffer, if available, without @@ -384,8 +388,9 @@ RingBuffer::readOne() } template +template int -RingBuffer::peek(T *const R__ destination, int n) const +RingBuffer::peek(S *const R__ destination, int n) const { int w = m_writer; int r = m_reader; @@ -394,7 +399,6 @@ RingBuffer::peek(T *const R__ destination, int n) const if (n > available) { std::cerr << "WARNING: RingBuffer::peek: " << n << " requested, only " << available << " available" << std::endl; - memset(destination + available, 0, (n - available) * sizeof(T)); n = available; } if (n == 0) return n; @@ -403,10 +407,10 @@ RingBuffer::peek(T *const R__ destination, int n) const const T *const R__ bufbase = m_buffer + r; if (here >= n) { - v_copy(destination, bufbase, n); + v_convert(destination, bufbase, n); } else { - v_copy(destination, bufbase, here); - v_copy(destination + here, m_buffer, n - here); + v_convert(destination, bufbase, here); + v_convert(destination + here, m_buffer, n - here); } return n; diff --git a/src/dsp/SampleFilter.h b/src/common/SampleFilter.h similarity index 83% rename from src/dsp/SampleFilter.h rename to src/common/SampleFilter.h index 21bd18f..74f197b 100644 --- a/src/dsp/SampleFilter.h +++ b/src/common/SampleFilter.h @@ -33,24 +33,11 @@ template class SampleFilter { public: - SampleFilter(int size) : m_size(size) { - assert(m_size > 0); - } - virtual ~SampleFilter() { } - - int getSize() const { return m_size; } - + virtual int getSize() const = 0; virtual void push(T) = 0; virtual T get() const = 0; virtual void reset() = 0; - -protected: - const int m_size; - -private: - SampleFilter(const SampleFilter &); - SampleFilter &operator=(const SampleFilter &); }; } diff --git a/src/base/Scavenger.h b/src/common/Scavenger.h similarity index 98% rename from src/base/Scavenger.h rename to src/common/Scavenger.h index b270371..e404314 100644 --- a/src/base/Scavenger.h +++ b/src/common/Scavenger.h @@ -33,9 +33,9 @@ #include #endif -#include "../system/Thread.h" -#include "../system/sysutils.h" -#include "../system/Allocators.h" +#include "Thread.h" +#include "sysutils.h" +#include "Allocators.h" //#define DEBUG_SCAVENGER 1 diff --git a/src/common/SingleThreadRingBuffer.h b/src/common/SingleThreadRingBuffer.h new file mode 100644 index 0000000..2e109ec --- /dev/null +++ b/src/common/SingleThreadRingBuffer.h @@ -0,0 +1,149 @@ +/* -*- 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-2022 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 RUBBERBAND_SINGLE_THREAD_RINGBUFFER_H +#define RUBBERBAND_SINGLE_THREAD_RINGBUFFER_H + +#include + +namespace RubberBand { + +/** + * SingleThreadRingBuffer implements a ring buffer to be used to store + * a sample type T, for reading and writing within a single + * thread. SingleThreadRingBuffer is a simple container, not a + * thread-safe lock-free structure: use RingBuffer for the situation + * with reader and writer in separate threads. Currently this + * implementation only supports reading and writing a single sample at + * a time. + */ +template +class SingleThreadRingBuffer +{ +public: + /** + * Create a ring buffer with room to write n samples. + * + * Note that the internal storage size will actually be n+1 + * samples, as one element is unavailable for administrative + * reasons. Since the ring buffer performs best if its size is a + * power of two, this means n should ideally be some power of two + * minus one. + */ + SingleThreadRingBuffer(int n) : + m_buffer(n + 1, T()), + m_writer(0), + m_reader(0), + m_size(n + 1) { } + + SingleThreadRingBuffer() : + m_buffer(1, T()), + m_writer(0), + m_reader(0), + m_size(1) { } + + SingleThreadRingBuffer (const SingleThreadRingBuffer &other) =default; + SingleThreadRingBuffer &operator=(const SingleThreadRingBuffer &other) =default; + + virtual ~SingleThreadRingBuffer() { } + + /** + * Return the total capacity of the ring buffer in samples. + * (This is the argument n passed to the constructor.) + */ + int getSize() const { + return m_size - 1; + } + + /** + * Reset read and write pointers, thus emptying the buffer. + */ + void reset() { + m_writer = m_reader; + } + + /** + * Return the amount of data available for reading, in samples. + */ + int getReadSpace() const { + if (m_writer > m_reader) return m_writer - m_reader; + else if (m_writer < m_reader) return (m_writer + m_size) - m_reader; + else return 0; + } + + /** + * Return the amount of space available for writing, in samples. + */ + int getWriteSpace() const { + int space = (m_reader + m_size - m_writer - 1); + if (space >= m_size) space -= m_size; + return space; + } + + /** + * Read one sample from the buffer. If no sample is available, + * silently return zero. + */ + T readOne() { + if (m_writer == m_reader) { + return {}; + } + auto value = m_buffer[m_reader]; + if (++m_reader == m_size) m_reader = 0; + return value; + } + + /** + * Pretend to read one sample from the buffer, without actually + * returning it (i.e. discard the next sample). + */ + void skipOne() { + if (m_writer == m_reader) { + return; + } + if (++m_reader == m_size) m_reader = 0; + } + + /** + * Write one sample to the buffer. If insufficient space is + * available, the sample will not be written. Returns the number + * of samples actually written, i.e. 0 or 1. + */ + int writeOne(const T &value) { + if (getWriteSpace() == 0) return 0; + m_buffer[m_writer] = value; + if (++m_writer == m_size) m_writer = 0; + return 1; + } + +protected: + std::vector m_buffer; + int m_writer; + int m_reader; + int m_size; +}; + +} + +#endif + diff --git a/src/common/StretchCalculator.cpp b/src/common/StretchCalculator.cpp new file mode 100644 index 0000000..7b98020 --- /dev/null +++ b/src/common/StretchCalculator.cpp @@ -0,0 +1,791 @@ +/* -*- 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-2022 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 "StretchCalculator.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "sysutils.h" + +namespace RubberBand +{ + +StretchCalculator::StretchCalculator(size_t sampleRate, + size_t inputIncrement, + bool useHardPeaks, + Log log) : + m_sampleRate(sampleRate), + m_increment(inputIncrement), + m_prevDf(0), + m_prevRatio(1.0), + m_prevTimeRatio(1.0), + m_justReset(true), + m_transientAmnesty(0), + m_debugLevel(0), + m_useHardPeaks(useHardPeaks), + m_inFrameCounter(0), + m_frameCheckpoint(0, 0), + m_outFrameCounter(0), + m_log(log) +{ + m_log.log(2, "StretchCalculator: useHardPeaks", useHardPeaks); +} + +StretchCalculator::~StretchCalculator() +{ +} + +void +StretchCalculator::setKeyFrameMap(const std::map &mapping) +{ + m_keyFrameMap = mapping; + + // Ensure we always have a 0 -> 0 mapping. If there's nothing in + // the map at all, don't need to worry about this (empty map is + // handled separately anyway) + if (!m_keyFrameMap.empty()) { + if (m_keyFrameMap.find(0) == m_keyFrameMap.end()) { + m_keyFrameMap[0] = 0; + } + } +} + +std::vector +StretchCalculator::calculate(double ratio, size_t inputDuration, + const std::vector &phaseResetDf) +{ + m_peaks = findPeaks(phaseResetDf); + + size_t totalCount = phaseResetDf.size(); + + size_t outputDuration = lrint(inputDuration * ratio); + + m_log.log(1, "StretchCalculator::calculate: inputDuration and ratio", inputDuration, ratio); + + outputDuration = lrint((phaseResetDf.size() * m_increment) * ratio); + + m_log.log(1, "StretchCalculator::calculate: outputDuration rounds up from and to", inputDuration * ratio, outputDuration); + m_log.log(1, "StretchCalculator::calculate: df size and increment", phaseResetDf.size(), m_increment); + + std::vector peaks; // peak position (in chunks) and hardness + std::vector targets; // targets for mapping peaks (in samples) + mapPeaks(peaks, targets, outputDuration, totalCount); + + m_log.log(2, "have fixed positions", peaks.size()); + + size_t totalInput = 0, totalOutput = 0; + + std::vector increments; + + for (size_t i = 0; i <= peaks.size(); ++i) { + + size_t regionStart, regionStartChunk, regionEnd, regionEndChunk; + bool phaseReset = false; + + if (i == 0) { + regionStartChunk = 0; + regionStart = 0; + } else { + regionStartChunk = peaks[i-1].chunk; + regionStart = targets[i-1]; + phaseReset = peaks[i-1].hard; + } + + if (i == peaks.size()) { + regionEndChunk = totalCount; + regionEnd = outputDuration; + } else { + regionEndChunk = peaks[i].chunk; + regionEnd = targets[i]; + } + + if (regionStartChunk > totalCount) regionStartChunk = totalCount; + if (regionStart > outputDuration) regionStart = outputDuration; + if (regionEndChunk > totalCount) regionEndChunk = totalCount; + if (regionEnd > outputDuration) regionEnd = outputDuration; + + if (regionEndChunk < regionStartChunk) regionEndChunk = regionStartChunk; + if (regionEnd < regionStart) regionEnd = regionStart; + + size_t regionDuration = regionEnd - regionStart; + + size_t nchunks = regionEndChunk - regionStartChunk; + + m_log.log(2, "region from and to (chunks)", regionStartChunk, regionEndChunk); + m_log.log(2, "region from and to (samples)", regionStart, regionEnd); + + if (nchunks == 0) { + m_log.log(2, "note: nchunks == 0"); + continue; + } + + double per = double(regionDuration) / double(nchunks); + double acc = 0.0; + size_t nremaining = nchunks; + size_t totalForRegion = 0; + + if (phaseReset) { + size_t incr; + if (nchunks > 1) { + incr = m_increment; + if (incr > regionDuration) { + incr = regionDuration; + } + } else { + incr = regionDuration; + } + increments.push_back(- int64_t(incr)); + per = double(regionDuration - incr) / double(nchunks - 1); + acc += incr; + totalForRegion += incr; + totalInput += m_increment; + nremaining = nremaining - 1; + } + + if (nremaining > 0) { + for (size_t j = 0; j+1 < nremaining; ++j) { + acc += per; + size_t incr = size_t(round(acc - totalForRegion)); + increments.push_back(incr); + totalForRegion += incr; + totalInput += m_increment; + } + if (regionDuration > totalForRegion) { + size_t final = regionDuration - totalForRegion; + increments.push_back(final); + totalForRegion += final; + totalInput += m_increment; + } + } + + totalOutput += totalForRegion; + } + + m_log.log(1, "total input (frames, chunks)", totalInput, totalInput / m_increment); + m_log.log(1, "total output and achieved ratio", totalOutput, double(totalOutput)/double(totalInput)); + m_log.log(1, "ideal output", totalInput * ratio); + + return increments; +} + +void +StretchCalculator::mapPeaks(std::vector &peaks, + std::vector &targets, + size_t outputDuration, + size_t totalCount) +{ + // outputDuration is in audio samples; totalCount is in chunks + + if (m_keyFrameMap.empty()) { + // "normal" behaviour -- fixed points are strictly in + // proportion + peaks = m_peaks; + for (size_t i = 0; i < peaks.size(); ++i) { + targets.push_back + (lrint((double(peaks[i].chunk) * outputDuration) / totalCount)); + } + return; + } + + // We have been given a set of source -> target sample frames in + // m_keyFrameMap. We want to ensure that (to the nearest chunk) these + // are followed exactly, and any fixed points that we calculated + // ourselves are interpolated in linear proportion in between. + + size_t peakidx = 0; + std::map::const_iterator mi = m_keyFrameMap.begin(); + + // NB we know for certain we have a mapping from 0 -> 0 (or at + // least, some mapping for source sample 0) because that is + // enforced in setKeyFrameMap above. However, we aren't guaranteed + // to have a mapping for the total duration -- we will usually + // need to assume it maps to the normal duration * ratio sample + + while (mi != m_keyFrameMap.end()) { + + // The map we've been given is from sample to sample, but + // we can only map from chunk to sample. We should perhaps + // adjust the target sample to compensate for the discrepancy + // between the chunk position and the exact requested source + // sample. But we aren't doing that yet. + + size_t sourceStartChunk = mi->first / m_increment; + size_t sourceEndChunk = totalCount; + + size_t targetStartSample = mi->second; + size_t targetEndSample = outputDuration; + + ++mi; + if (mi != m_keyFrameMap.end()) { + sourceEndChunk = mi->first / m_increment; + targetEndSample = mi->second; + } + + if (sourceStartChunk >= totalCount || + sourceStartChunk >= sourceEndChunk || + targetStartSample >= outputDuration || + targetStartSample >= targetEndSample) { + m_log.log(0, "NOTE: ignoring key-frame mapping from chunk to sample", sourceStartChunk, targetStartSample); + m_log.log(0, "(source or target chunk exceeds total count, or end is not later than start)"); + continue; + } + + // one peak and target for the mapping, then one for each of + // the computed peaks that appear before the following mapping + + Peak p; + p.chunk = sourceStartChunk; + p.hard = false; // mappings are in time only, not phase reset points + peaks.push_back(p); + targets.push_back(targetStartSample); + + m_log.log(2, "mapped key-frame chunk to frame", sourceStartChunk, targetStartSample); + + while (peakidx < m_peaks.size()) { + + size_t pchunk = m_peaks[peakidx].chunk; + + if (pchunk < sourceStartChunk) { + // shouldn't happen, should have been dealt with + // already -- but no harm in ignoring it explicitly + ++peakidx; + continue; + } + if (pchunk == sourceStartChunk) { + // convert that last peak to a hard one, after all + peaks[peaks.size()-1].hard = true; + ++peakidx; + continue; + } + if (pchunk >= sourceEndChunk) { + // leave the rest for after the next mapping + break; + } + p.chunk = pchunk; + p.hard = m_peaks[peakidx].hard; + + double proportion = + double(pchunk - sourceStartChunk) / + double(sourceEndChunk - sourceStartChunk); + + size_t target = + targetStartSample + + lrint(proportion * + (targetEndSample - targetStartSample)); + + if (target <= targets[targets.size()-1] + m_increment) { + // peaks will become too close together afterwards, ignore + ++peakidx; + continue; + } + + m_log.log(2, "mapped peak chunk to frame", pchunk, target); + + peaks.push_back(p); + targets.push_back(target); + ++peakidx; + } + } +} + +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, + bool alignFrameStarts) +{ + 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 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 = (!m_justReset) && (ratio != m_prevRatio); + m_justReset = false; + + 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 + + m_log.log(2, "StretchCalculator: ratio changed from and to", m_prevRatio, ratio); + + int64_t toCheckpoint = expectedOutFrame + (m_inFrameCounter, m_prevTimeRatio); + m_frameCheckpoint = + std::pair(m_inFrameCounter, toCheckpoint); + } + + m_prevRatio = ratio; + m_prevTimeRatio = timeRatio; + + if (m_log.getDebugLevel() > 2) { + std::ostringstream os; + os << "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 + << "\n"; + + os << "inFrameCounter = " << m_inFrameCounter + << ", outFrameCounter = " << m_outFrameCounter + << "\n"; + + os << "The next sample out is input sample " << m_inFrameCounter << "\n"; + m_log.log(3, os.str().c_str()); + } + + int64_t intended, projected; + if (alignFrameStarts) { // R3 + intended = expectedOutFrame(m_inFrameCounter, timeRatio); + projected = + int64_t(round(m_outFrameCounter)); + } else { // R2 + intended = expectedOutFrame + (m_inFrameCounter + analysisWindowSize/4, timeRatio); + projected = + int64_t(round(m_outFrameCounter + + (synthesisWindowSize/4 * effectivePitchRatio))); + } + + int64_t divergence = projected - intended; + + m_log.log(3, "for current frame + quarter frame: intended vs projected", intended, projected); + m_log.log(3, "divergence", divergence); + + // 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 + // from the ratio directly. For the moment we're happy if it + // works well in common situations. + + float transientThreshold = 0.35f; +// if (ratio > 1) transientThreshold = 0.25f; + + if (m_useHardPeaks && df > m_prevDf * 1.1f && df > transientThreshold) { + if (divergence > 1000 || divergence < -1000) { + m_log.log(2, "StretchCalculator::calculateSingle: transient, but we're not permitting it because the divergence is too great", divergence); + } else { + isTransient = true; + } + } + + m_log.log(3, "df and prevDf", df, m_prevDf); + + m_prevDf = df; + + if (m_transientAmnesty > 0) { + if (isTransient) { + m_log.log(2, "StretchCalculator::calculateSingle: transient, but we have an amnesty: df and threshold", df, transientThreshold); + isTransient = false; + } + --m_transientAmnesty; + } + + if (isTransient) { + m_log.log(2, "StretchCalculator::calculateSingle: transient: df and threshold", df, transientThreshold); + + // as in offline mode, 0.05 sec approx min between transients + m_transientAmnesty = + lrint(ceil(double(m_sampleRate) / (20 * double(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); + + int level = (divergence != 0 ? 2 : 3); + m_log.log(level, "divergence and recovery", divergence, recovery); + m_log.log(level, "outIncrement and adjusted incr", outIncrement, 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; + } + + m_log.log(level, "clamped into", minIncr, maxIncr); + m_log.log(level, "giving incr", incr); + + if (incr < 0) { + m_log.log(0, "WARNING: internal error: incr < 0 in calculateSingle"); + outIncrement = 0; + } else { + outIncrement = incr; + } + } + + m_log.log(2, "StretchCalculator::calculateSingle: returning isTransient and outIncrement", isTransient, outIncrement); + + m_inFrameCounter += inIncrement; + m_outFrameCounter += outIncrement * effectivePitchRatio; + + if (isTransient) { + return -outIncrement; + } else { + return outIncrement; + } +} + +void +StretchCalculator::reset() +{ + m_prevDf = 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(); + + m_justReset = true; +} + +std::vector +StretchCalculator::findPeaks(const std::vector &rawDf) +{ + std::vector df = smoothDF(rawDf); + + // We distinguish between "soft" and "hard" peaks. A soft peak is + // simply the result of peak-picking on the smoothed onset + // detection function, and it represents any (strong-ish) onset. + // We aim to ensure always that soft peaks are placed at the + // correct position in time. A hard peak is where there is a very + // rapid rise in detection function, and it presumably represents + // a more broadband, noisy transient. For these we perform a + // phase reset (if in the appropriate mode), and we locate the + // reset at the first point where we notice enough of a rapid + // rise, rather than necessarily at the peak itself, in order to + // preserve the shape of the transient. + + std::set hardPeakCandidates; + std::set softPeakCandidates; + + if (m_useHardPeaks) { + + // 0.05 sec approx min between hard peaks + size_t hardPeakAmnesty = lrint(ceil(double(m_sampleRate) / + (20 * double(m_increment)))); + size_t prevHardPeak = 0; + + m_log.log(2, "hardPeakAmnesty", hardPeakAmnesty); + + for (size_t i = 1; i + 1 < df.size(); ++i) { + + if (df[i] < 0.1) continue; + if (df[i] <= df[i-1] * 1.1) continue; + if (df[i] < 0.22) continue; + + if (!hardPeakCandidates.empty() && + i < prevHardPeak + hardPeakAmnesty) { + continue; + } + + bool hard = (df[i] > 0.4); + + if (hard) { + m_log.log(2, "hard peak, df > absolute 0.4: chunk and df", i, df[i]); + } + + if (!hard) { + hard = (df[i] > df[i-1] * 1.4); + + if (hard) { + m_log.log(2, "hard peak, single rise of 40%: chunk and df", i, df[i]); + } + } + + if (!hard && i > 1) { + hard = (df[i] > df[i-1] * 1.2 && + df[i-1] > df[i-2] * 1.2); + + if (hard) { + m_log.log(2, "hard peak, two rises of 20%: chunk and df", i, df[i]); + } + } + + if (!hard && i > 2) { + // have already established that df[i] > df[i-1] * 1.1 + hard = (df[i] > 0.3 && + df[i-1] > df[i-2] * 1.1 && + df[i-2] > df[i-3] * 1.1); + + if (hard) { + m_log.log(2, "hard peak, three rises of 10%: chunk and df", i, df[i]); + } + } + + if (!hard) continue; + + size_t peakLocation = i; + + if (i + 1 < rawDf.size() && + rawDf[i + 1] > rawDf[i] * 1.4) { + + ++peakLocation; + + m_log.log(2, "big rise next, pushing hard peak forward to", peakLocation); + } + + hardPeakCandidates.insert(peakLocation); + prevHardPeak = peakLocation; + } + } + + size_t medianmaxsize = lrint(ceil(double(m_sampleRate) / + double(m_increment))); // 1 sec ish + + m_log.log(2, "mediansize", medianmaxsize); + if (medianmaxsize < 7) { + medianmaxsize = 7; + m_log.log(2, "adjusted mediansize", medianmaxsize); + } + + int minspacing = lrint(ceil(double(m_sampleRate) / + (20 * double(m_increment)))); // 0.05 sec ish + + std::deque medianwin; + std::vector sorted; + int softPeakAmnesty = 0; + + for (size_t i = 0; i < medianmaxsize/2; ++i) { + medianwin.push_back(0); + } + for (size_t i = 0; i < medianmaxsize/2 && i < df.size(); ++i) { + medianwin.push_back(df[i]); + } + + size_t lastSoftPeak = 0; + + for (size_t i = 0; i < df.size(); ++i) { + + size_t mediansize = medianmaxsize; + + if (medianwin.size() < mediansize) { + mediansize = medianwin.size(); + } + + size_t middle = medianmaxsize / 2; + if (middle >= mediansize) middle = mediansize-1; + + size_t nextDf = i + mediansize - middle; + + if (mediansize < 2) { + if (mediansize > medianmaxsize) { // absurd, but never mind that + medianwin.pop_front(); + } + if (nextDf < df.size()) { + medianwin.push_back(df[nextDf]); + } else { + medianwin.push_back(0); + } + continue; + } + + sorted.clear(); + for (size_t j = 0; j < mediansize; ++j) { + sorted.push_back(medianwin[j]); + } + std::sort(sorted.begin(), sorted.end()); + + size_t n = 90; // percentile above which we pick peaks + size_t index = (sorted.size() * n) / 100; + if (index >= sorted.size()) index = sorted.size()-1; + if (index == sorted.size()-1 && index > 0) --index; + float thresh = sorted[index]; + + if (medianwin[middle] > thresh && + medianwin[middle] > medianwin[middle-1] && + medianwin[middle] > medianwin[middle+1] && + softPeakAmnesty == 0) { + + size_t maxindex = middle; + float maxval = medianwin[middle]; + + for (size_t j = middle+1; j < mediansize; ++j) { + if (medianwin[j] > maxval) { + maxval = medianwin[j]; + maxindex = j; + } else if (medianwin[j] < medianwin[middle]) { + break; + } + } + + size_t peak = i + maxindex - middle; + + if (softPeakCandidates.empty() || lastSoftPeak != peak) { + m_log.log(2, "soft peak: chunk and median df", peak, medianwin[middle]); + if (peak >= df.size()) { + m_log.log(2, "peak is beyond end"); + } else { + softPeakCandidates.insert(peak); + lastSoftPeak = peak; + } + } + + softPeakAmnesty = minspacing + maxindex - middle; + m_log.log(3, "amnesty", softPeakAmnesty); + + } else if (softPeakAmnesty > 0) --softPeakAmnesty; + + if (mediansize >= medianmaxsize) { + medianwin.pop_front(); + } + if (nextDf < df.size()) { + medianwin.push_back(df[nextDf]); + } else { + medianwin.push_back(0); + } + } + + std::vector peaks; + + while (!hardPeakCandidates.empty() || !softPeakCandidates.empty()) { + + bool haveHardPeak = !hardPeakCandidates.empty(); + bool haveSoftPeak = !softPeakCandidates.empty(); + + size_t hardPeak = (haveHardPeak ? *hardPeakCandidates.begin() : 0); + size_t softPeak = (haveSoftPeak ? *softPeakCandidates.begin() : 0); + + Peak peak; + peak.hard = false; + peak.chunk = softPeak; + + bool ignore = false; + + if (haveHardPeak && + (!haveSoftPeak || hardPeak <= softPeak)) { + m_log.log(3, "hard peak", hardPeak); + peak.hard = true; + peak.chunk = hardPeak; + hardPeakCandidates.erase(hardPeakCandidates.begin()); + } else { + m_log.log(3, "soft peak", softPeak); + if (!peaks.empty() && + peaks[peaks.size()-1].hard && + peaks[peaks.size()-1].chunk + 3 >= softPeak) { + m_log.log(3, "ignoring, as we just had a hard peak"); + ignore = true; + } + } + + if (haveSoftPeak && peak.chunk == softPeak) { + softPeakCandidates.erase(softPeakCandidates.begin()); + } + + if (!ignore) { + peaks.push_back(peak); + } + } + + return peaks; +} + +std::vector +StretchCalculator::smoothDF(const std::vector &df) +{ + std::vector smoothedDF; + + for (size_t i = 0; i < df.size(); ++i) { + // three-value moving mean window for simple smoothing + float total = 0.f, count = 0; + if (i > 0) { total += df[i-1]; ++count; } + total += df[i]; ++count; + if (i+1 < df.size()) { total += df[i+1]; ++count; } + float mean = total / count; + smoothedDF.push_back(mean); + } + + return smoothedDF; +} + +} + diff --git a/src/StretchCalculator.h b/src/common/StretchCalculator.h similarity index 82% rename from src/StretchCalculator.h rename to src/common/StretchCalculator.h index 416b0da..af12780 100644 --- a/src/StretchCalculator.h +++ b/src/common/StretchCalculator.h @@ -30,13 +30,18 @@ #include #include +#include "Log.h" + namespace RubberBand { class StretchCalculator { public: - StretchCalculator(size_t sampleRate, size_t inputIncrement, bool useHardPeaks); + StretchCalculator(size_t sampleRate, + size_t inputIncrement, + bool useHardPeaks, + Log log); virtual ~StretchCalculator(); /** @@ -51,13 +56,10 @@ public: /** * Calculate phase increments for a region of audio, given the * overall target stretch ratio, input duration in audio samples, - * and the audio curves to use for identifying phase lock points - * (lockAudioCurve) and for allocating stretches to relatively - * less prominent points (stretchAudioCurve). + * and the audio curves to use for identifying phase lock points. */ std::vector calculate(double ratio, size_t inputDuration, - const std::vector &lockAudioCurve, - const std::vector &stretchAudioCurve); + const std::vector &lockAudioCurve); /** * Calculate the phase increment for a single audio block, given @@ -74,7 +76,8 @@ public: float curveValue, size_t increment, size_t analysisWindowSize, - size_t synthesisWindowSize); + size_t synthesisWindowSize, + bool alignFrameStarts); void setUseHardPeaks(bool use) { m_useHardPeaks = use; } @@ -96,21 +99,12 @@ protected: void mapPeaks(std::vector &peaks, std::vector &targets, size_t outputDuration, size_t totalCount); - std::vector distributeRegion(const std::vector ®ionCurve, - size_t outputDuration, float ratio, - bool phaseReset); - - void calculateDisplacements(const std::vector &df, - float &maxDf, - double &totalDisplacement, - double &maxDisplacement, - float adj) const; - size_t m_sampleRate; size_t m_increment; float m_prevDf; double m_prevRatio; double m_prevTimeRatio; + bool m_justReset; int m_transientAmnesty; // only in RT mode; handled differently offline int m_debugLevel; bool m_useHardPeaks; @@ -118,6 +112,7 @@ protected: std::pair m_frameCheckpoint; int64_t expectedOutFrame(int64_t inFrame, double timeRatio); double m_outFrameCounter; + Log m_log; std::map m_keyFrameMap; std::vector m_peaks; diff --git a/src/system/Thread.cpp b/src/common/Thread.cpp similarity index 100% rename from src/system/Thread.cpp rename to src/common/Thread.cpp diff --git a/src/system/Thread.h b/src/common/Thread.h similarity index 100% rename from src/system/Thread.h rename to src/common/Thread.h diff --git a/src/system/VectorOps.h b/src/common/VectorOps.h similarity index 100% rename from src/system/VectorOps.h rename to src/common/VectorOps.h diff --git a/src/system/VectorOpsComplex.cpp b/src/common/VectorOpsComplex.cpp similarity index 100% rename from src/system/VectorOpsComplex.cpp rename to src/common/VectorOpsComplex.cpp diff --git a/src/system/VectorOpsComplex.h b/src/common/VectorOpsComplex.h similarity index 98% rename from src/system/VectorOpsComplex.h rename to src/common/VectorOpsComplex.h index 782afa2..1229a77 100644 --- a/src/system/VectorOpsComplex.h +++ b/src/common/VectorOpsComplex.h @@ -33,8 +33,6 @@ namespace RubberBand { template inline void c_phasor(T *real, T *imag, T phase) { - //!!! IPP contains ippsSinCos_xxx in ippvm.h -- these are - //!!! fixed-accuracy, test and compare #if defined HAVE_VDSP int one = 1; if (sizeof(T) == sizeof(float)) { diff --git a/src/dsp/Window.h b/src/common/Window.h similarity index 59% rename from src/dsp/Window.h rename to src/common/Window.h index c6c9b81..2800c7e 100644 --- a/src/dsp/Window.h +++ b/src/common/Window.h @@ -28,9 +28,9 @@ #include #include -#include "../system/sysutils.h" -#include "../system/VectorOps.h" -#include "../system/Allocators.h" +#include "sysutils.h" +#include "VectorOps.h" +#include "Allocators.h" namespace RubberBand { @@ -38,12 +38,14 @@ enum WindowType { RectangularWindow, BartlettWindow, HammingWindow, - HanningWindow, + HannWindow, BlackmanWindow, GaussianWindow, ParzenWindow, NuttallWindow, - BlackmanHarrisWindow + BlackmanHarrisWindow, + NiemitaloForwardWindow, + NiemitaloReverseWindow }; template @@ -79,6 +81,10 @@ public: v_multiply(dst, src, m_cache, m_size); } + inline void cutAndAdd(const T *const R__ src, T *const R__ dst) const { + v_multiply_and_add(dst, src, m_cache, m_size); + } + inline void add(T *const R__ dst, T scale) const { v_add_with_gain(dst, m_cache, scale, m_size); } @@ -105,7 +111,7 @@ protected: T m_area; void encache(); - void cosinewin(T *, T, T, T, T); + void cosinewin(T *, double, double, double, double); }; template @@ -136,7 +142,7 @@ void Window::encache() cosinewin(m_cache, 0.54, 0.46, 0.0, 0.0); break; - case HanningWindow: + case HannWindow: cosinewin(m_cache, 0.50, 0.50, 0.0, 0.0); break; @@ -174,6 +180,64 @@ void Window::encache() case BlackmanHarrisWindow: cosinewin(m_cache, 0.35875, 0.48829, 0.14128, 0.01168); break; + + case NiemitaloForwardWindow: + case NiemitaloReverseWindow: + { + /* Interesting asymmetric window proposed by Olli Niemitalo. + https://dsp.stackexchange.com/questions/2337/fft-with-asymmetric-windowing + (Olli also writes "I cross-license all of my code and + images in Stack Exchange under CC0 1.0" - + https://dsp.stackexchange.com/users/15347/olli-niemitalo) + */ + int quarter = n/4; + int eighth = n/8; + int k = 0; + for (int i = 0; i < n - eighth - quarter; ++i) { + T x = 2.0 * M_PI * + (((T(k + quarter) + 0.5) / T(n)) - 1.75); + m_cache[k++] = + 2.57392230162633461887 + - 1.58661480271141974718 * cos(x) + + 3.80257516644523141380 * sin(x) + - 1.93437090055110760822 * cos(2.0 * x) + - 3.27163999159752183488 * sin(2.0 * x) + + 3.26617449847621266201 * cos(3.0 * x) + - 0.30335261753524439543 * sin(3.0 * x) + - 0.92126091064427817479 * cos(4.0 * x) + + 2.33100177294084742741 * sin(4.0 * x) + - 1.19953922321306438725 * cos(5.0 * x) + - 1.25098147932225423062 * sin(5.0 * x) + + 0.99132076607048635886 * cos(6.0 * x) + - 0.34506787787355830410 * sin(6.0 * x) + - 0.04028033685700077582 * cos(7.0 * x) + + 0.55461815542612269425 * sin(7.0 * x) + - 0.21882110175036428856 * cos(8.0 * x) + - 0.10756484378756643594 * sin(8.0 * x) + + 0.06025986430527170007 * cos(9.0 * x) + - 0.05777077835678736534 * sin(9.0 * x) + + 0.00920984524892982936 * cos(10.0 * x) + + 0.01501989089735343216 * sin(10.0 * x); + } + for (int i = 0; i < eighth; ++i) { + int j = eighth - 1 - i; + m_cache[k++] = + (1.0 - m_cache[n/2 - 1 - j] * m_cache[n/2 + j]) / + m_cache[n/4 + j]; + } + for (int i = 0; i < quarter; ++i) { + m_cache[k++] = 0.0; + } + + if (m_type == NiemitaloReverseWindow) { + for (int i = 0; i < n/2; ++i) { + T tmp = m_cache[i]; + m_cache[i] = m_cache[n - i - 1]; + m_cache[n - i - 1] = tmp; + } + } + } + } m_area = 0; @@ -184,14 +248,14 @@ void Window::encache() } template -void Window::cosinewin(T *mult, T a0, T a1, T a2, T a3) +void Window::cosinewin(T *mult, double a0, double a1, double a2, double a3) { int n = int(m_size); for (int i = 0; i < n; ++i) { - mult[i] *= (a0 - - a1 * cos(2 * M_PI * i / n) - + a2 * cos(4 * M_PI * i / n) - - a3 * cos(6 * M_PI * i / n)); + mult[i] = T(mult[i] * (a0 + - a1 * cos(2 * M_PI * i / n) + + a2 * cos(4 * M_PI * i / n) + - a3 * cos(6 * M_PI * i / n))); } } diff --git a/src/common/mathmisc.h b/src/common/mathmisc.h new file mode 100644 index 0000000..e6697ff --- /dev/null +++ b/src/common/mathmisc.h @@ -0,0 +1,56 @@ +/* -*- 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-2022 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 RUBBERBAND_MATHMISC_H +#define RUBBERBAND_MATHMISC_H + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif // M_PI + +namespace RubberBand { + +inline double mod(double x, double y) { + return x - (y * floor(x / y)); +} +inline float modf(float x, float y) { + return x - (y * float(floor(x / y))); +} + +inline double princarg(double a) { + return mod(a + M_PI, -2.0 * M_PI) + M_PI; +} +inline float princargf(float a) { + return modf(a + (float)M_PI, -2.f * (float)M_PI) + (float)M_PI; +} + +inline int binForFrequency(double f, int fftSize, double sampleRate) { + return int(round(f * double(fftSize) / sampleRate)); +} +inline double frequencyForBin(int b, int fftSize, double sampleRate) { + return (double(b) * sampleRate) / double(fftSize); +} + +} + +#endif diff --git a/src/system/sysutils.cpp b/src/common/sysutils.cpp similarity index 88% rename from src/system/sysutils.cpp rename to src/common/sysutils.cpp index 8ae09d8..6f36ae0 100644 --- a/src/system/sysutils.cpp +++ b/src/common/sysutils.cpp @@ -28,7 +28,6 @@ #include #include #else /* !_WIN32 */ -#include #include #ifdef __APPLE__ #include @@ -165,11 +164,6 @@ void gettimeofday(struct timeval *tv, void *tz) tv->tv_sec = (long)((now.ns100 - 116444736000000000LL) / 10000000LL); } -void usleep(unsigned long usec) -{ - ::Sleep(usec == 0 ? 0 : usec < 1000 ? 1 : usec / 1000); -} - #endif void system_specific_initialise() @@ -209,31 +203,6 @@ void system_specific_application_initialise() { } - -ProcessStatus -system_get_process_status(int pid) -{ -#ifdef _WIN32 - HANDLE handle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); - if (!handle) { - return ProcessNotRunning; - } else { - CloseHandle(handle); - return ProcessRunning; - } -#else - if (kill(getpid(), 0) == 0) { - if (kill(pid, 0) == 0) { - return ProcessRunning; - } else { - return ProcessNotRunning; - } - } else { - return UnknownProcessStatus; - } -#endif -} - #ifdef _WIN32 void system_memorybarrier() { diff --git a/src/system/sysutils.h b/src/common/sysutils.h similarity index 79% rename from src/system/sysutils.h rename to src/common/sysutils.h index a6271cd..9bcdd9a 100644 --- a/src/system/sysutils.h +++ b/src/common/sysutils.h @@ -45,6 +45,12 @@ # define R__ #endif +#ifdef __clang__ +# define RTENTRY__ __attribute__((annotate("realtime"))) +#else +# define RTENTRY__ +#endif + #if defined(_MSC_VER) # include # include @@ -82,33 +88,22 @@ namespace RubberBand { +#ifdef PROCESS_SAMPLE_TYPE +typedef PROCESS_SAMPLE_TYPE process_t; +#else +typedef double process_t; +#endif + extern const char *system_get_platform_tag(); extern bool system_is_multiprocessor(); extern void system_specific_initialise(); extern void system_specific_application_initialise(); -enum ProcessStatus { ProcessRunning, ProcessNotRunning, UnknownProcessStatus }; -extern ProcessStatus system_get_process_status(int pid); - #ifdef _WIN32 struct timeval { long tv_sec; long tv_usec; }; void gettimeofday(struct timeval *p, void *tz); #endif // _WIN32 -#ifdef _MSC_VER -void usleep(unsigned long); -#endif // _MSC_VER - -inline double mod(double x, double y) { return x - (y * floor(x / y)); } -inline float modf(float x, float y) { return x - (y * float(floor(x / y))); } - -#ifndef M_PI -#define M_PI 3.14159265358979323846 -#endif // M_PI - -inline double princarg(double a) { return mod(a + M_PI, -2.0 * M_PI) + M_PI; } -inline float princargf(float a) { return modf(a + (float)M_PI, -2.f * (float)M_PI) + (float)M_PI; } - } // end namespace // The following should be functions in the RubberBand namespace, really @@ -124,11 +119,6 @@ extern void system_memorybarrier(); } #define MBARRIER() RubberBand::system_memorybarrier() -#define DLOPEN(a,b) LoadLibrary((a).toStdWString().c_str()) -#define DLSYM(a,b) GetProcAddress((HINSTANCE)(a),(b)) -#define DLCLOSE(a) FreeLibrary((HINSTANCE)(a)) -#define DLERROR() "" - #else // !_WIN32 #include @@ -157,11 +147,6 @@ extern void system_memorybarrier(); # endif #endif -#define DLOPEN(a,b) dlopen((a).toStdString().c_str(),(b)) -#define DLSYM(a,b) dlsym((a),(b)) -#define DLCLOSE(a) dlclose((a)) -#define DLERROR() dlerror() - #endif // !_WIN32 #ifdef NO_THREADING diff --git a/src/dsp/MovingMedian.h b/src/dsp/MovingMedian.h deleted file mode 100644 index ba18390..0000000 --- a/src/dsp/MovingMedian.h +++ /dev/null @@ -1,110 +0,0 @@ -/* -*- 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-2022 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 RUBBERBAND_MOVING_MEDIAN_H -#define RUBBERBAND_MOVING_MEDIAN_H - -#include "SampleFilter.h" - -#include "../system/Allocators.h" - -#include - -#include - -namespace RubberBand -{ - -template -class MovingMedian : public SampleFilter -{ - typedef SampleFilter P; - -public: - MovingMedian(int size, float percentile = 50.f) : - SampleFilter(size), - m_frame(allocate_and_zero(size)), - m_sorted(allocate_and_zero(size)), - m_sortend(m_sorted + P::m_size - 1) { - setPercentile(percentile); - } - - ~MovingMedian() { - deallocate(m_frame); - deallocate(m_sorted); - } - - void setPercentile(float p) { - m_index = int((P::m_size * p) / 100.f); - if (m_index >= P::m_size) m_index = P::m_size-1; - if (m_index < 0) m_index = 0; - } - - void push(T value) { - if (value != value) { - std::cerr << "WARNING: MovingMedian: NaN encountered" << std::endl; - value = T(); - } - drop(m_frame[0]); - v_move(m_frame, m_frame+1, P::m_size-1); - m_frame[P::m_size-1] = value; - put(value); - } - - T get() const { - return m_sorted[m_index]; - } - - void reset() { - v_zero(m_frame, P::m_size); - v_zero(m_sorted, P::m_size); - } - -private: - T *const m_frame; - T *const m_sorted; - T *const m_sortend; - int m_index; - - void put(T value) { - // precondition: m_sorted contains m_size-1 values, packed at start - // postcondition: m_sorted contains m_size values, one of which is value - T *index = std::lower_bound(m_sorted, m_sortend, value); - v_move(index + 1, index, m_sortend - index); - *index = value; - } - - void drop(T value) { - // precondition: m_sorted contains m_size values, one of which is value - // postcondition: m_sorted contains m_size-1 values, packed at start - T *index = std::lower_bound(m_sorted, m_sortend + 1, value); - assert(*index == value); - v_move(index, index + 1, m_sortend - index); - *m_sortend = T(0); - } -}; - -} - -#endif - diff --git a/src/float_cast/float_cast.h b/src/ext/float_cast/float_cast.h similarity index 100% rename from src/float_cast/float_cast.h rename to src/ext/float_cast/float_cast.h diff --git a/src/getopt/getopt.c b/src/ext/getopt/getopt.c similarity index 100% rename from src/getopt/getopt.c rename to src/ext/getopt/getopt.c diff --git a/src/getopt/getopt.h b/src/ext/getopt/getopt.h similarity index 100% rename from src/getopt/getopt.h rename to src/ext/getopt/getopt.h diff --git a/src/getopt/getopt_long.c b/src/ext/getopt/getopt_long.c similarity index 100% rename from src/getopt/getopt_long.c rename to src/ext/getopt/getopt_long.c diff --git a/src/getopt/unistd.h b/src/ext/getopt/unistd.h similarity index 100% rename from src/getopt/unistd.h rename to src/ext/getopt/unistd.h diff --git a/src/kissfft/COPYING b/src/ext/kissfft/COPYING similarity index 100% rename from src/kissfft/COPYING rename to src/ext/kissfft/COPYING diff --git a/src/kissfft/_kiss_fft_guts.h b/src/ext/kissfft/_kiss_fft_guts.h similarity index 100% rename from src/kissfft/_kiss_fft_guts.h rename to src/ext/kissfft/_kiss_fft_guts.h diff --git a/src/kissfft/kiss_fft.c b/src/ext/kissfft/kiss_fft.c similarity index 100% rename from src/kissfft/kiss_fft.c rename to src/ext/kissfft/kiss_fft.c diff --git a/src/kissfft/kiss_fft.h b/src/ext/kissfft/kiss_fft.h similarity index 100% rename from src/kissfft/kiss_fft.h rename to src/ext/kissfft/kiss_fft.h diff --git a/src/kissfft/kiss_fft_log.h b/src/ext/kissfft/kiss_fft_log.h similarity index 100% rename from src/kissfft/kiss_fft_log.h rename to src/ext/kissfft/kiss_fft_log.h diff --git a/src/kissfft/kiss_fftr.c b/src/ext/kissfft/kiss_fftr.c similarity index 100% rename from src/kissfft/kiss_fftr.c rename to src/ext/kissfft/kiss_fftr.c diff --git a/src/kissfft/kiss_fftr.h b/src/ext/kissfft/kiss_fftr.h similarity index 100% rename from src/kissfft/kiss_fftr.h rename to src/ext/kissfft/kiss_fftr.h diff --git a/src/pommier/neon_mathfun.h b/src/ext/pommier/neon_mathfun.h similarity index 100% rename from src/pommier/neon_mathfun.h rename to src/ext/pommier/neon_mathfun.h diff --git a/src/pommier/sse_mathfun.h b/src/ext/pommier/sse_mathfun.h similarity index 100% rename from src/pommier/sse_mathfun.h rename to src/ext/pommier/sse_mathfun.h diff --git a/src/speex/COPYING b/src/ext/speex/COPYING similarity index 100% rename from src/speex/COPYING rename to src/ext/speex/COPYING diff --git a/src/speex/resample.c b/src/ext/speex/resample.c similarity index 100% rename from src/speex/resample.c rename to src/ext/speex/resample.c diff --git a/src/speex/speex_resampler.h b/src/ext/speex/speex_resampler.h similarity index 100% rename from src/speex/speex_resampler.h rename to src/ext/speex/speex_resampler.h diff --git a/src/dsp/AudioCurveCalculator.cpp b/src/faster/AudioCurveCalculator.cpp similarity index 100% rename from src/dsp/AudioCurveCalculator.cpp rename to src/faster/AudioCurveCalculator.cpp diff --git a/src/dsp/AudioCurveCalculator.h b/src/faster/AudioCurveCalculator.h similarity index 99% rename from src/dsp/AudioCurveCalculator.h rename to src/faster/AudioCurveCalculator.h index 0af1c9a..90c77b6 100644 --- a/src/dsp/AudioCurveCalculator.h +++ b/src/faster/AudioCurveCalculator.h @@ -26,7 +26,7 @@ #include -#include "../system/sysutils.h" +#include "../common/sysutils.h" namespace RubberBand { diff --git a/src/audiocurves/CompoundAudioCurve.cpp b/src/faster/CompoundAudioCurve.cpp similarity index 99% rename from src/audiocurves/CompoundAudioCurve.cpp rename to src/faster/CompoundAudioCurve.cpp index 509a8c2..14cf8d8 100644 --- a/src/audiocurves/CompoundAudioCurve.cpp +++ b/src/faster/CompoundAudioCurve.cpp @@ -23,7 +23,7 @@ #include "CompoundAudioCurve.h" -#include "../dsp/MovingMedian.h" +#include "../common/MovingMedian.h" #include diff --git a/src/audiocurves/CompoundAudioCurve.h b/src/faster/CompoundAudioCurve.h similarity index 98% rename from src/audiocurves/CompoundAudioCurve.h rename to src/faster/CompoundAudioCurve.h index 74bb3bd..6e5cfbb 100644 --- a/src/audiocurves/CompoundAudioCurve.h +++ b/src/faster/CompoundAudioCurve.h @@ -26,7 +26,7 @@ #include "PercussiveAudioCurve.h" #include "HighFrequencyAudioCurve.h" -#include "../dsp/SampleFilter.h" +#include "../common/SampleFilter.h" namespace RubberBand { diff --git a/src/audiocurves/HighFrequencyAudioCurve.cpp b/src/faster/HighFrequencyAudioCurve.cpp similarity index 100% rename from src/audiocurves/HighFrequencyAudioCurve.cpp rename to src/faster/HighFrequencyAudioCurve.cpp diff --git a/src/audiocurves/HighFrequencyAudioCurve.h b/src/faster/HighFrequencyAudioCurve.h similarity index 97% rename from src/audiocurves/HighFrequencyAudioCurve.h rename to src/faster/HighFrequencyAudioCurve.h index a071817..43bed56 100644 --- a/src/audiocurves/HighFrequencyAudioCurve.h +++ b/src/faster/HighFrequencyAudioCurve.h @@ -24,7 +24,7 @@ #ifndef RUBBERBAND_HIGHFREQUENCY_AUDIO_CURVE_H #define RUBBERBAND_HIGHFREQUENCY_AUDIO_CURVE_H -#include "../dsp/AudioCurveCalculator.h" +#include "AudioCurveCalculator.h" namespace RubberBand { diff --git a/src/audiocurves/PercussiveAudioCurve.cpp b/src/faster/PercussiveAudioCurve.cpp similarity index 97% rename from src/audiocurves/PercussiveAudioCurve.cpp rename to src/faster/PercussiveAudioCurve.cpp index 02db7c6..afea381 100644 --- a/src/audiocurves/PercussiveAudioCurve.cpp +++ b/src/faster/PercussiveAudioCurve.cpp @@ -23,8 +23,8 @@ #include "PercussiveAudioCurve.h" -#include "../system/Allocators.h" -#include "../system/VectorOps.h" +#include "../common/Allocators.h" +#include "../common/VectorOps.h" #include #include diff --git a/src/audiocurves/PercussiveAudioCurve.h b/src/faster/PercussiveAudioCurve.h similarity index 97% rename from src/audiocurves/PercussiveAudioCurve.h rename to src/faster/PercussiveAudioCurve.h index e2be7da..4aa90a2 100644 --- a/src/audiocurves/PercussiveAudioCurve.h +++ b/src/faster/PercussiveAudioCurve.h @@ -24,7 +24,7 @@ #ifndef RUBBERBAND_PERCUSSIVE_AUDIO_CURVE_H #define RUBBERBAND_PERCUSSIVE_AUDIO_CURVE_H -#include "../dsp/AudioCurveCalculator.h" +#include "AudioCurveCalculator.h" namespace RubberBand { diff --git a/src/StretcherImpl.cpp b/src/faster/R2Stretcher.cpp similarity index 72% rename from src/StretcherImpl.cpp rename to src/faster/R2Stretcher.cpp index cb9d2f0..46e044c 100644 --- a/src/StretcherImpl.cpp +++ b/src/faster/R2Stretcher.cpp @@ -21,23 +21,18 @@ you must obtain a valid commercial licence before doing so. */ -#include "StretcherImpl.h" +#include "R2Stretcher.h" -#include "audiocurves/PercussiveAudioCurve.h" -#include "audiocurves/HighFrequencyAudioCurve.h" -#include "audiocurves/SpectralDifferenceAudioCurve.h" -#include "audiocurves/SilentAudioCurve.h" -#include "audiocurves/ConstantAudioCurve.h" -#include "audiocurves/CompoundAudioCurve.h" - -#include "dsp/Resampler.h" - -#include "StretchCalculator.h" +#include "PercussiveAudioCurve.h" +#include "HighFrequencyAudioCurve.h" +#include "SilentAudioCurve.h" +#include "CompoundAudioCurve.h" #include "StretcherChannelData.h" -#include "base/Profiler.h" - -#include "system/sysutils.h" +#include "../common/StretchCalculator.h" +#include "../common/Resampler.h" +#include "../common/Profiler.h" +#include "../common/sysutils.h" #include #include @@ -45,10 +40,6 @@ #include #include -using namespace RubberBand; - -using std::cerr; -using std::endl; using std::vector; using std::map; using std::set; @@ -58,21 +49,19 @@ using std::min; namespace RubberBand { const size_t -RubberBandStretcher::Impl::m_defaultIncrement = 256; +R2Stretcher::m_defaultIncrement = 256; const size_t -RubberBandStretcher::Impl::m_defaultFftSize = 2048; - -int -RubberBandStretcher::Impl::m_defaultDebugLevel = 0; +R2Stretcher::m_defaultFftSize = 2048; static bool _initialised = false; -RubberBandStretcher::Impl::Impl(size_t sampleRate, - size_t channels, - Options options, - double initialTimeRatio, - double initialPitchScale) : +R2Stretcher::R2Stretcher(size_t sampleRate, + size_t channels, + RubberBandStretcher::Options options, + double initialTimeRatio, + double initialPitchScale, + Log log) : m_sampleRate(sampleRate), m_channels(channels), m_timeRatio(initialTimeRatio), @@ -89,7 +78,7 @@ RubberBandStretcher::Impl::Impl(size_t sampleRate, #endif m_realtime(false), m_options(options), - m_debugLevel(m_defaultDebugLevel), + m_log(log), m_mode(JustCreated), m_awindow(0), m_afilter(0), @@ -105,7 +94,6 @@ RubberBandStretcher::Impl::Impl(size_t sampleRate, m_lastProcessPhaseResetDf(16), m_emergencyScavenger(10, 4), m_phaseResetAudioCurve(0), - m_stretchAudioCurve(0), m_silentAudioCurve(0), m_stretchCalculator(0), m_freq0(600), @@ -118,9 +106,10 @@ RubberBandStretcher::Impl::Impl(size_t sampleRate, _initialised = true; } - if (m_debugLevel > 0) { - cerr << "RubberBandStretcher::Impl::Impl: rate = " << m_sampleRate << ", options = " << options << endl; - } + m_log.log(1, "R2Stretcher::R2Stretcher: rate, options", + m_sampleRate, options); + m_log.log(1, "R2Stretcher::R2Stretcher: initial time ratio and pitch scale", + m_timeRatio, m_pitchScale); // Window size will vary according to the audio sample rate, but // we don't let it drop below the 48k default @@ -128,19 +117,17 @@ RubberBandStretcher::Impl::Impl(size_t sampleRate, // if (m_rateMultiple < 1.f) m_rateMultiple = 1.f; m_baseFftSize = roundUp(int(m_defaultFftSize * m_rateMultiple)); - if ((options & OptionWindowShort) || (options & OptionWindowLong)) { - if ((options & OptionWindowShort) && (options & OptionWindowLong)) { - cerr << "RubberBandStretcher::Impl::Impl: Cannot specify OptionWindowLong and OptionWindowShort together; falling back to OptionWindowStandard" << endl; - } else if (options & OptionWindowShort) { + if ((options & RubberBandStretcher::OptionWindowShort) || + (options & RubberBandStretcher::OptionWindowLong)) { + if ((options & RubberBandStretcher::OptionWindowShort) && + (options & RubberBandStretcher::OptionWindowLong)) { + m_log.log(0, "R2Stretcher::R2Stretcher: Cannot specify OptionWindowLong and OptionWindowShort together; falling back to OptionWindowStandard"); + } else if (options & RubberBandStretcher::OptionWindowShort) { m_baseFftSize = m_baseFftSize / 2; - if (m_debugLevel > 0) { - cerr << "setting baseFftSize to " << m_baseFftSize << endl; - } - } else if (options & OptionWindowLong) { + m_log.log(1, "setting baseFftSize", m_baseFftSize); + } else if (options & RubberBandStretcher::OptionWindowLong) { m_baseFftSize = m_baseFftSize * 2; - if (m_debugLevel > 0) { - cerr << "setting baseFftSize to " << m_baseFftSize << endl; - } + m_log.log(1, "setting baseFftSize", m_baseFftSize); } m_fftSize = m_baseFftSize; m_aWindowSize = m_baseFftSize; @@ -149,13 +136,8 @@ RubberBandStretcher::Impl::Impl(size_t sampleRate, m_maxProcessSize = m_aWindowSize; } - if (m_options & OptionProcessRealTime) { - + if (m_options & RubberBandStretcher::OptionProcessRealTime) { m_realtime = true; - - if (!(m_options & OptionStretchPrecise)) { - m_options |= OptionStretchPrecise; - } } #ifndef NO_THREADING @@ -165,15 +147,15 @@ RubberBandStretcher::Impl::Impl(size_t sampleRate, if (m_realtime) { m_threaded = false; - } else if (m_options & OptionThreadingNever) { + } else if (m_options & RubberBandStretcher::OptionThreadingNever) { m_threaded = false; - } else if (!(m_options & OptionThreadingAlways) && + } else if (!(m_options & RubberBandStretcher::OptionThreadingAlways) && !system_is_multiprocessor()) { m_threaded = false; } - if (m_threaded && m_debugLevel > 0) { - cerr << "Going multithreaded..." << endl; + if (m_threaded) { + m_log.log(1, "Going multithreaded..."); } } #endif @@ -181,16 +163,14 @@ RubberBandStretcher::Impl::Impl(size_t sampleRate, configure(); } -RubberBandStretcher::Impl::~Impl() +R2Stretcher::~R2Stretcher() { #ifndef NO_THREADING if (m_threaded) { MutexLocker locker(&m_threadSetMutex); for (set::iterator i = m_threadSet.begin(); i != m_threadSet.end(); ++i) { - if (m_debugLevel > 0) { - cerr << "RubberBandStretcher::~RubberBandStretcher: joining (channel " << *i << ")" << endl; - } + m_log.log(1, "RubberBandStretcher::~RubberBandStretcher: joining for channel", (*i)->channel()); (*i)->abandon(); (*i)->wait(); delete *i; @@ -203,7 +183,6 @@ RubberBandStretcher::Impl::~Impl() } delete m_phaseResetAudioCurve; - delete m_stretchAudioCurve; delete m_silentAudioCurve; delete m_stretchCalculator; delete m_studyFFT; @@ -219,16 +198,14 @@ RubberBandStretcher::Impl::~Impl() } void -RubberBandStretcher::Impl::reset() +R2Stretcher::reset() { #ifndef NO_THREADING if (m_threaded) { m_threadSetMutex.lock(); for (set::iterator i = m_threadSet.begin(); i != m_threadSet.end(); ++i) { - if (m_debugLevel > 0) { - cerr << "RubberBandStretcher::~RubberBandStretcher: joining (channel " << *i << ")" << endl; - } + m_log.log(1, "RubberBandStretcher::~RubberBandStretcher: joining for channel", (*i)->channel()); (*i)->abandon(); (*i)->wait(); delete *i; @@ -249,7 +226,6 @@ RubberBandStretcher::Impl::reset() m_mode = JustCreated; if (m_phaseResetAudioCurve) m_phaseResetAudioCurve->reset(); - if (m_stretchAudioCurve) m_stretchAudioCurve->reset(); if (m_silentAudioCurve) m_silentAudioCurve->reset(); m_inputDuration = 0; m_silentHistory = 0; @@ -262,11 +238,11 @@ RubberBandStretcher::Impl::reset() } void -RubberBandStretcher::Impl::setTimeRatio(double ratio) +R2Stretcher::setTimeRatio(double ratio) { if (!m_realtime) { if (m_mode == Studying || m_mode == Processing) { - cerr << "RubberBandStretcher::Impl::setTimeRatio: Cannot set ratio while studying or processing in non-RT mode" << endl; + m_log.log(0, "R2Stretcher::setTimeRatio: Cannot set ratio while studying or processing in non-RT mode"); return; } } @@ -278,11 +254,11 @@ RubberBandStretcher::Impl::setTimeRatio(double ratio) } void -RubberBandStretcher::Impl::setPitchScale(double fs) +R2Stretcher::setPitchScale(double fs) { if (!m_realtime) { if (m_mode == Studying || m_mode == Processing) { - cerr << "RubberBandStretcher::Impl::setPitchScale: Cannot set ratio while studying or processing in non-RT mode" << endl; + m_log.log(0, "R2Stretcher::setPitchScale: Cannot set ratio while studying or processing in non-RT mode"); return; } } @@ -296,7 +272,7 @@ RubberBandStretcher::Impl::setPitchScale(double fs) reconfigure(); - if (!(m_options & OptionPitchHighConsistency) && + if (!(m_options & RubberBandStretcher::OptionPitchHighConsistency) && (was1 || resampleBeforeStretching() != rbs) && m_pitchScale != 1.f) { @@ -310,19 +286,19 @@ RubberBandStretcher::Impl::setPitchScale(double fs) } double -RubberBandStretcher::Impl::getTimeRatio() const +R2Stretcher::getTimeRatio() const { return m_timeRatio; } double -RubberBandStretcher::Impl::getPitchScale() const +R2Stretcher::getPitchScale() const { return m_pitchScale; } void -RubberBandStretcher::Impl::setExpectedInputDuration(size_t samples) +R2Stretcher::setExpectedInputDuration(size_t samples) { if (samples == m_expectedInputDuration) return; m_expectedInputDuration = samples; @@ -331,7 +307,7 @@ RubberBandStretcher::Impl::setExpectedInputDuration(size_t samples) } void -RubberBandStretcher::Impl::setMaxProcessSize(size_t samples) +R2Stretcher::setMaxProcessSize(size_t samples) { if (samples <= m_maxProcessSize) return; m_maxProcessSize = samples; @@ -340,15 +316,15 @@ RubberBandStretcher::Impl::setMaxProcessSize(size_t samples) } void -RubberBandStretcher::Impl::setKeyFrameMap(const std::map & +R2Stretcher::setKeyFrameMap(const std::map & mapping) { if (m_realtime) { - cerr << "RubberBandStretcher::Impl::setKeyFrameMap: Cannot specify key frame map in RT mode" << endl; + m_log.log(0, "R2Stretcher::setKeyFrameMap: Cannot specify key frame map in RT mode"); return; } if (m_mode == Processing) { - cerr << "RubberBandStretcher::Impl::setKeyFrameMap: Cannot specify key frame map after process() has begun" << endl; + m_log.log(0, "R2Stretcher::setKeyFrameMap: Cannot specify key frame map after process() has begun"); return; } @@ -358,7 +334,7 @@ RubberBandStretcher::Impl::setKeyFrameMap(const std::map & } float -RubberBandStretcher::Impl::getFrequencyCutoff(int n) const +R2Stretcher::getFrequencyCutoff(int n) const { switch (n) { case 0: return m_freq0; @@ -369,7 +345,7 @@ RubberBandStretcher::Impl::getFrequencyCutoff(int n) const } void -RubberBandStretcher::Impl::setFrequencyCutoff(int n, float f) +R2Stretcher::setFrequencyCutoff(int n, float f) { switch (n) { case 0: m_freq0 = f; break; @@ -379,7 +355,7 @@ RubberBandStretcher::Impl::setFrequencyCutoff(int n, float f) } double -RubberBandStretcher::Impl::getEffectiveRatio() const +R2Stretcher::getEffectiveRatio() const { // Returns the ratio that the internal time stretcher needs to // achieve, not the resulting duration ratio of the output (which @@ -396,7 +372,7 @@ RubberBandStretcher::Impl::getEffectiveRatio() const } size_t -RubberBandStretcher::Impl::roundUp(size_t value) +R2Stretcher::roundUp(size_t value) { if (!(value & (value - 1))) return value; int bits = 0; @@ -406,7 +382,7 @@ RubberBandStretcher::Impl::roundUp(size_t value) } void -RubberBandStretcher::Impl::calculateSizes() +R2Stretcher::calculateSizes() { size_t inputIncrement = m_defaultIncrement; size_t windowSize = m_baseFftSize; @@ -416,12 +392,12 @@ RubberBandStretcher::Impl::calculateSizes() // This special case is likelier than one might hope, because // of naive initialisations in programs that set it from a // variable - std::cerr << "RubberBandStretcher: WARNING: Pitch scale must be greater than zero!\nResetting it from " << m_pitchScale << " to the default of 1.0: no pitch change will occur" << std::endl; + m_log.log(0, "WARNING: Pitch scale must be greater than zero! Resetting it to default, no pitch shift will happen", m_pitchScale); m_pitchScale = 1.0; } if (m_timeRatio <= 0.0) { // Likewise - std::cerr << "RubberBandStretcher: WARNING: Time ratio must be greater than zero!\nResetting it from " << m_timeRatio << " to the default of 1.0: no time stretch will occur" << std::endl; + m_log.log(0, "WARNING: Time ratio must be greater than zero! Resetting it to default, no time stretch will happen", m_timeRatio); m_timeRatio = 1.0; } @@ -474,7 +450,7 @@ RubberBandStretcher::Impl::calculateSizes() if (windowSize < minwin) windowSize = minwin; if (rsb) { -// cerr << "adjusting window size from " << windowSize; + size_t oldWindowSize = windowSize; size_t newWindowSize = roundUp(lrint(windowSize / m_pitchScale)); if (newWindowSize < 512) newWindowSize = 512; size_t div = windowSize / newWindowSize; @@ -483,7 +459,8 @@ RubberBandStretcher::Impl::calculateSizes() outputIncrement /= div; windowSize /= div; } -// cerr << " to " << windowSize << " (inputIncrement = " << inputIncrement << ", outputIncrement = " << outputIncrement << ")" << endl; + m_log.log(2, "adjusting window size from/to", oldWindowSize, windowSize); + m_log.log(2, "input and output increments", inputIncrement, outputIncrement); } } @@ -526,7 +503,7 @@ RubberBandStretcher::Impl::calculateSizes() m_fftSize = windowSize; - if (m_options & OptionSmoothingOn) { + if (m_options & RubberBandStretcher::OptionSmoothingOn) { m_aWindowSize = windowSize * 2; m_sWindowSize = windowSize * 2; } else { @@ -543,10 +520,11 @@ RubberBandStretcher::Impl::calculateSizes() // twice the basic output increment (i.e. input increment times // ratio) for any chunk. - if (m_debugLevel > 0) { - 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; - } + m_log.log(1, "calculateSizes: time ratio and pitch scale", m_timeRatio, m_pitchScale); + m_log.log(1, "effective ratio", getEffectiveRatio()); + m_log.log(1, "analysis and synthesis window sizes", m_aWindowSize, m_sWindowSize); + m_log.log(1, "fft size", m_fftSize); + m_log.log(1, "input increment and mean output increment", m_increment, m_increment * getEffectiveRatio()); if (std::max(m_aWindowSize, m_sWindowSize) > m_maxProcessSize) { m_maxProcessSize = std::max(m_aWindowSize, m_sWindowSize); @@ -574,18 +552,19 @@ RubberBandStretcher::Impl::calculateSizes() #endif } - if (m_debugLevel > 0) { - cerr << "calculateSizes: outbuf size = " << m_outbufSize << endl; - } + m_log.log(1, "calculateSizes: outbuf size", m_outbufSize); } void -RubberBandStretcher::Impl::configure() +R2Stretcher::configure() { - if (m_debugLevel > 0) { - std::cerr << "configure[" << this << "]: realtime = " << m_realtime << ", pitch scale = " - << m_pitchScale << ", channels = " << m_channels << std::endl; - } + if (m_realtime) { + m_log.log(1, "configure, realtime: pitch scale and channels", + m_pitchScale, m_channels); + } else { + m_log.log(1, "configure, offline: pitch scale and channels", + m_pitchScale, m_channels); + } size_t prevFftSize = m_fftSize; size_t prevAWindowSize = m_aWindowSize; @@ -629,7 +608,7 @@ RubberBandStretcher::Impl::configure() for (set::const_iterator i = windowSizes.begin(); i != windowSizes.end(); ++i) { if (m_windows.find(*i) == m_windows.end()) { - m_windows[*i] = new Window(HanningWindow, *i); + m_windows[*i] = new Window(HannWindow, *i); } if (m_sincs.find(*i) == m_sincs.end()) { m_sincs[*i] = new SincWindow(*i, *i); @@ -639,9 +618,8 @@ RubberBandStretcher::Impl::configure() m_afilter = m_sincs[m_aWindowSize]; m_swindow = m_windows[m_sWindowSize]; - if (m_debugLevel > 0) { - cerr << "Window area: " << m_awindow->getArea() << "; synthesis window area: " << m_swindow->getArea() << endl; - } + m_log.log(1, "analysis and synthesis window areas", + m_awindow->getArea(), m_swindow->getArea()); } if (windowSizeChanged || outbufSizeChanged) { @@ -662,12 +640,12 @@ RubberBandStretcher::Impl::configure() if (!m_realtime && fftSizeChanged) { delete m_studyFFT; - m_studyFFT = new FFT(m_fftSize, m_debugLevel); + m_studyFFT = new FFT(m_fftSize, m_log.getDebugLevel()); m_studyFFT->initFloat(); } if (m_pitchScale != 1.0 || - (m_options & OptionPitchHighConsistency) || + (m_options & RubberBandStretcher::OptionPitchHighConsistency) || m_realtime) { for (size_t c = 0; c < m_channels; ++c) { @@ -687,7 +665,8 @@ RubberBandStretcher::Impl::configure() } params.maxBufferSize = 4096 * 16; - params.debugLevel = (m_debugLevel > 0 ? m_debugLevel-1 : 0); + int myLevel = m_log.getDebugLevel(); + params.debugLevel = (myLevel > 0 ? myLevel-1 : 0); m_channelData[c]->resampler = new Resampler(params, 1); @@ -701,10 +680,6 @@ RubberBandStretcher::Impl::configure() } } - // stretchAudioCurve is unused in RT mode; phaseResetAudioCurve, - // silentAudioCurve and stretchCalculator however are used in all - // modes - delete m_phaseResetAudioCurve; m_phaseResetAudioCurve = new CompoundAudioCurve (CompoundAudioCurve::Parameters(m_sampleRate, m_fftSize)); @@ -714,23 +689,13 @@ RubberBandStretcher::Impl::configure() m_silentAudioCurve = new SilentAudioCurve (SilentAudioCurve::Parameters(m_sampleRate, m_fftSize)); - if (!m_realtime) { - delete m_stretchAudioCurve; - if (!(m_options & OptionStretchPrecise)) { - m_stretchAudioCurve = new SpectralDifferenceAudioCurve - (SpectralDifferenceAudioCurve::Parameters(m_sampleRate, m_fftSize)); - } else { - m_stretchAudioCurve = new ConstantAudioCurve - (ConstantAudioCurve::Parameters(m_sampleRate, m_fftSize)); - } - } - delete m_stretchCalculator; m_stretchCalculator = new StretchCalculator (m_sampleRate, m_increment, - !(m_options & OptionTransientsSmooth)); + !(m_options & RubberBandStretcher::OptionTransientsSmooth), + m_log); - m_stretchCalculator->setDebugLevel(m_debugLevel); + m_stretchCalculator->setDebugLevel(m_log.getDebugLevel()); m_inputDuration = 0; // Prepare the inbufs with half a chunk of emptiness. The centre @@ -745,19 +710,18 @@ RubberBandStretcher::Impl::configure() // want gaps when the ratio changes. if (!m_realtime) { - if (m_debugLevel > 1) { - cerr << "Not real time mode: prefilling with " << m_aWindowSize/2 << " samples" << endl; - } + m_log.log(1, "offline mode: prefilling with", m_aWindowSize/2); for (size_t c = 0; c < m_channels; ++c) { m_channelData[c]->reset(); m_channelData[c]->inbuf->zero(m_aWindowSize/2); } + } else { + m_log.log(1, "realtime mode: no prefill"); } } - void -RubberBandStretcher::Impl::reconfigure() +R2Stretcher::reconfigure() { if (!m_realtime) { if (m_mode == Studying) { @@ -765,7 +729,6 @@ RubberBandStretcher::Impl::reconfigure() // the df vectors calculateStretch(); m_phaseResetDf.clear(); - m_stretchDf.clear(); m_silence.clear(); m_inputDuration = 0; } @@ -792,17 +755,17 @@ RubberBandStretcher::Impl::reconfigure() m_sWindowSize != prevSWindowSize) { if (m_windows.find(m_aWindowSize) == m_windows.end()) { - std::cerr << "WARNING: reconfigure(): window allocation (size " << m_aWindowSize << ") required in RT mode" << std::endl; + m_log.log(0, "WARNING: reconfigure(): window allocation required in realtime mode, size", m_aWindowSize); m_windows[m_aWindowSize] = new Window - (HanningWindow, m_aWindowSize); + (HannWindow, m_aWindowSize); m_sincs[m_aWindowSize] = new SincWindow (m_aWindowSize, m_aWindowSize); } if (m_windows.find(m_sWindowSize) == m_windows.end()) { - std::cerr << "WARNING: reconfigure(): window allocation (size " << m_sWindowSize << ") required in RT mode" << std::endl; + m_log.log(0, "WARNING: reconfigure(): window allocation required in realtime mode, size", m_sWindowSize); m_windows[m_sWindowSize] = new Window - (HanningWindow, m_sWindowSize); + (HannWindow, m_sWindowSize); m_sincs[m_sWindowSize] = new SincWindow (m_sWindowSize, m_sWindowSize); } @@ -831,14 +794,15 @@ RubberBandStretcher::Impl::reconfigure() if (m_channelData[c]->resampler) continue; - std::cerr << "WARNING: reconfigure(): resampler construction required in RT mode" << std::endl; + m_log.log(0, "WARNING: reconfigure(): resampler construction required in RT mode"); Resampler::Parameters params; params.quality = Resampler::FastestTolerable; params.dynamism = Resampler::RatioOftenChanging; params.ratioChange = Resampler::SmoothRatioChange; params.maxBufferSize = m_sWindowSize; - params.debugLevel = (m_debugLevel > 0 ? m_debugLevel-1 : 0); + int myLevel = m_log.getDebugLevel(); + params.debugLevel = (myLevel > 0 ? myLevel-1 : 0); m_channelData[c]->resampler = new Resampler(params, 1); @@ -854,59 +818,61 @@ RubberBandStretcher::Impl::reconfigure() if (m_fftSize != prevFftSize) { m_phaseResetAudioCurve->setFftSize(m_fftSize); m_silentAudioCurve->setFftSize(m_fftSize); - 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; - } + if (somethingChanged) { + m_log.log(1, "reconfigure: at least one parameter changed"); + } else { + m_log.log(1, "reconfigure: nothing changed"); } } size_t -RubberBandStretcher::Impl::getLatency() const +R2Stretcher::getLatency() const { if (!m_realtime) return 0; return lrint((m_aWindowSize/2) / m_pitchScale); } void -RubberBandStretcher::Impl::setTransientsOption(Options options) +R2Stretcher::setTransientsOption(RubberBandStretcher::Options options) { if (!m_realtime) { - cerr << "RubberBandStretcher::Impl::setTransientsOption: Not permissible in non-realtime mode" << endl; + m_log.log(0, "R2Stretcher::setTransientsOption: Not permissible in non-realtime mode"); return; } - int mask = (OptionTransientsMixed | OptionTransientsSmooth | OptionTransientsCrisp); + int mask = (RubberBandStretcher::OptionTransientsMixed | + RubberBandStretcher::OptionTransientsSmooth | + RubberBandStretcher::OptionTransientsCrisp); m_options &= ~mask; options &= mask; m_options |= options; m_stretchCalculator->setUseHardPeaks - (!(m_options & OptionTransientsSmooth)); + (!(m_options & RubberBandStretcher::OptionTransientsSmooth)); } void -RubberBandStretcher::Impl::setDetectorOption(Options options) +R2Stretcher::setDetectorOption(RubberBandStretcher::Options options) { if (!m_realtime) { - cerr << "RubberBandStretcher::Impl::setDetectorOption: Not permissible in non-realtime mode" << endl; + m_log.log(0, "R2Stretcher::setDetectorOption: Not permissible in non-realtime mode"); return; } - int mask = (OptionDetectorPercussive | OptionDetectorCompound | OptionDetectorSoft); + int mask = (RubberBandStretcher::OptionDetectorPercussive | + RubberBandStretcher::OptionDetectorCompound | + RubberBandStretcher::OptionDetectorSoft); m_options &= ~mask; options &= mask; m_options |= options; CompoundAudioCurve::Type dt = CompoundAudioCurve::CompoundDetector; - if (m_options & OptionDetectorPercussive) dt = CompoundAudioCurve::PercussiveDetector; - else if (m_options & OptionDetectorSoft) dt = CompoundAudioCurve::SoftDetector; + if (m_options & RubberBandStretcher::OptionDetectorPercussive) { + dt = CompoundAudioCurve::PercussiveDetector; + } else if (m_options & RubberBandStretcher::OptionDetectorSoft) { + dt = CompoundAudioCurve::SoftDetector; + } if (dt == m_detectorType) return; m_detectorType = dt; @@ -917,36 +883,38 @@ RubberBandStretcher::Impl::setDetectorOption(Options options) } void -RubberBandStretcher::Impl::setPhaseOption(Options options) +R2Stretcher::setPhaseOption(RubberBandStretcher::Options options) { - int mask = (OptionPhaseLaminar | OptionPhaseIndependent); + int mask = (RubberBandStretcher::OptionPhaseLaminar | + RubberBandStretcher::OptionPhaseIndependent); m_options &= ~mask; options &= mask; m_options |= options; } void -RubberBandStretcher::Impl::setFormantOption(Options options) +R2Stretcher::setFormantOption(RubberBandStretcher::Options options) { - int mask = (OptionFormantShifted | OptionFormantPreserved); + int mask = (RubberBandStretcher::OptionFormantShifted | + RubberBandStretcher::OptionFormantPreserved); m_options &= ~mask; options &= mask; m_options |= options; } void -RubberBandStretcher::Impl::setPitchOption(Options options) +R2Stretcher::setPitchOption(RubberBandStretcher::Options options) { if (!m_realtime) { - cerr << "RubberBandStretcher::Impl::setPitchOption: Pitch option is not used in non-RT mode" << endl; + m_log.log(0, "R2Stretcher::setPitchOption: Pitch option is not used in non-RT mode"); return; } - Options prior = m_options; + RubberBandStretcher::Options prior = m_options; - int mask = (OptionPitchHighQuality | - OptionPitchHighSpeed | - OptionPitchHighConsistency); + int mask = (RubberBandStretcher::OptionPitchHighQuality | + RubberBandStretcher::OptionPitchHighSpeed | + RubberBandStretcher::OptionPitchHighConsistency); m_options &= ~mask; options &= mask; m_options |= options; @@ -955,19 +923,17 @@ RubberBandStretcher::Impl::setPitchOption(Options options) } void -RubberBandStretcher::Impl::study(const float *const *input, size_t samples, bool final) +R2Stretcher::study(const float *const *input, size_t samples, bool final) { - Profiler profiler("RubberBandStretcher::Impl::study"); + Profiler profiler("R2Stretcher::study"); if (m_realtime) { - if (m_debugLevel > 1) { - cerr << "RubberBandStretcher::Impl::study: Not meaningful in realtime mode" << endl; - } + m_log.log(0, "R2Stretcher::study: Not meaningful in realtime mode"); return; } if (m_mode == Processing || m_mode == Finished) { - cerr << "RubberBandStretcher::Impl::study: Cannot study after processing" << endl; + m_log.log(0, "R2Stretcher::study: Cannot study after processing"); return; } m_mode = Studying; @@ -1010,7 +976,8 @@ RubberBandStretcher::Impl::study(const float *const *input, size_t samples, bool if (writable == 0) { // warn - cerr << "WARNING: writable == 0 (consumed = " << consumed << ", samples = " << samples << ")" << endl; + m_log.log(0, "WARNING: writable == 0: consumed, samples", + consumed, samples); } else { inbuf.write(mixdown + consumed, writable); consumed += writable; @@ -1069,13 +1036,10 @@ RubberBandStretcher::Impl::study(const float *const *input, size_t samples, bool // cout << m_phaseResetDf.size() << " [" << final << "] -> " << df << " \t: "; - df = m_stretchAudioCurve->processFloat(cd.fltbuf, m_increment); - m_stretchDf.push_back(df); - df = m_silentAudioCurve->processFloat(cd.fltbuf, m_increment); bool silent = (df > 0.f); - if (silent && m_debugLevel > 1) { - cerr << "silence found at " << m_inputDuration << endl; + if (silent) { + m_log.log(2, "silence at", m_inputDuration); } m_silence.push_back(silent); @@ -1089,7 +1053,6 @@ RubberBandStretcher::Impl::study(const float *const *input, size_t samples, bool // extra afterwards. m_inputDuration += m_increment; -// cerr << "incr input duration by increment: " << m_increment << " -> " << m_inputDuration << endl; inbuf.skip(m_increment); } } @@ -1097,8 +1060,6 @@ RubberBandStretcher::Impl::study(const float *const *input, size_t samples, bool if (final) { int rs = inbuf.getReadSpace(); m_inputDuration += rs; -// cerr << "incr input duration by read space: " << rs << " -> " << m_inputDuration << endl; - if (m_inputDuration > m_aWindowSize/2) { // deducting the extra m_inputDuration -= m_aWindowSize/2; } @@ -1108,7 +1069,7 @@ RubberBandStretcher::Impl::study(const float *const *input, size_t samples, bool } vector -RubberBandStretcher::Impl::getOutputIncrements() const +R2Stretcher::getOutputIncrements() const { if (!m_realtime) { return m_outputIncrements; @@ -1122,7 +1083,7 @@ RubberBandStretcher::Impl::getOutputIncrements() const } vector -RubberBandStretcher::Impl::getPhaseResetCurve() const +R2Stretcher::getPhaseResetCurve() const { if (!m_realtime) { return m_phaseResetDf; @@ -1136,7 +1097,7 @@ RubberBandStretcher::Impl::getPhaseResetCurve() const } vector -RubberBandStretcher::Impl::getExactTimePoints() const +R2Stretcher::getExactTimePoints() const { std::vector points; if (!m_realtime) { @@ -1150,41 +1111,23 @@ RubberBandStretcher::Impl::getExactTimePoints() const } void -RubberBandStretcher::Impl::calculateStretch() +R2Stretcher::calculateStretch() { - Profiler profiler("RubberBandStretcher::Impl::calculateStretch"); + Profiler profiler("R2Stretcher::calculateStretch"); size_t inputDuration = m_inputDuration; if (!m_realtime && m_expectedInputDuration > 0) { if (m_expectedInputDuration != inputDuration) { - std::cerr << "RubberBandStretcher: WARNING: Actual study() duration differs from duration set by setExpectedInputDuration (" << m_inputDuration << " vs " << m_expectedInputDuration << ", diff = " << (m_expectedInputDuration - m_inputDuration) << "), using the latter for calculation" << std::endl; + m_log.log(0, "WARNING: Actual study() duration differs from duration set by setExpectedInputDuration - using the latter for calculation", m_inputDuration, m_expectedInputDuration); inputDuration = m_expectedInputDuration; } } -/* - double prdm = 0, sdm = 0; - if (!m_phaseResetDf.empty()) { - for (int i = 0; i < (int)m_phaseResetDf.size(); ++i) { - prdm += m_phaseResetDf[i]; - } - prdm /= m_phaseResetDf.size(); - } - if (!m_stretchDf.empty()) { - for (int i = 0; i < (int)m_stretchDf.size(); ++i) { - sdm += m_stretchDf[i]; - } - sdm /= m_stretchDf.size(); - } - std::cerr << "phase reset df mean = " << prdm << ", stretch df mean = " << sdm << std::endl; -*/ - std::vector increments = m_stretchCalculator->calculate (getEffectiveRatio(), inputDuration, - m_phaseResetDf, - m_stretchDf); + m_phaseResetDf); int history = 0; for (size_t i = 0; i < increments.size(); ++i) { @@ -1193,10 +1136,7 @@ RubberBandStretcher::Impl::calculateStretch() else history = 0; if (history >= int(m_aWindowSize / m_increment) && increments[i] >= 0) { increments[i] = -increments[i]; - if (m_debugLevel > 1) { - std::cerr << "phase reset on silence (silent history == " - << history << ")" << std::endl; - } + m_log.log(2, "phase reset on silence: silent history", history); } } @@ -1211,16 +1151,18 @@ RubberBandStretcher::Impl::calculateStretch() } void -RubberBandStretcher::Impl::setDebugLevel(int level) +R2Stretcher::setDebugLevel(int level) { - m_debugLevel = level; - if (m_stretchCalculator) m_stretchCalculator->setDebugLevel(level); + m_log.setDebugLevel(level); + if (m_stretchCalculator) { + m_stretchCalculator->setDebugLevel(level); + } } size_t -RubberBandStretcher::Impl::getSamplesRequired() const +R2Stretcher::getSamplesRequired() const { - Profiler profiler("RubberBandStretcher::Impl::getSamplesRequired"); + Profiler profiler("R2Stretcher::getSamplesRequired"); size_t reqd = 0; @@ -1235,9 +1177,7 @@ RubberBandStretcher::Impl::getSamplesRequired() const size_t rs = inbuf.getReadSpace(); size_t ws = outbuf.getReadSpace(); - if (m_debugLevel > 2) { - cerr << "getSamplesRequired: ws = " << ws << ", rs = " << rs << ", m_aWindowSize = " << m_aWindowSize << endl; - } + m_log.log(3, "getSamplesRequired: ws and rs ", ws, rs); // We should never return zero in non-threaded modes if // available() would also return zero, i.e. if ws == 0. If we @@ -1269,12 +1209,12 @@ RubberBandStretcher::Impl::getSamplesRequired() const } void -RubberBandStretcher::Impl::process(const float *const *input, size_t samples, bool final) +R2Stretcher::process(const float *const *input, size_t samples, bool final) { - Profiler profiler("RubberBandStretcher::Impl::process"); + Profiler profiler("R2Stretcher::process"); if (m_mode == Finished) { - cerr << "RubberBandStretcher::Impl::process: Cannot process again after final chunk" << endl; + m_log.log(0, "R2Stretcher::process: Cannot process again after final chunk"); return; } @@ -1287,9 +1227,7 @@ RubberBandStretcher::Impl::process(const float *const *input, size_t samples, bo if (!m_realtime) { // See note in configure() above. Of course, we should // never enter Studying unless we are non-RT anyway - if (m_debugLevel > 1) { - cerr << "Not real time mode: prefilling" << endl; - } + m_log.log(1, "offline mode: prefilling with", m_aWindowSize/2); for (size_t c = 0; c < m_channels; ++c) { m_channelData[c]->reset(); m_channelData[c]->inbuf->zero(m_aWindowSize/2); @@ -1306,10 +1244,8 @@ RubberBandStretcher::Impl::process(const float *const *input, size_t samples, bo m_threadSet.insert(thread); thread->start(); } - - if (m_debugLevel > 0) { - cerr << m_channels << " threads created" << endl; - } + + m_log.log(1, "created threads", m_channels); } #endif @@ -1341,12 +1277,10 @@ RubberBandStretcher::Impl::process(const float *const *input, size_t samples, bo final); if (consumed[c] < samples) { allConsumed = false; -// cerr << "process: waiting on input consumption for channel " << c << endl; } else { if (final) { m_channelData[c]->inputSize = m_channelData[c]->inCount; } -// cerr << "process: happy with channel " << c << endl; } if ( #ifndef NO_THREADING @@ -1379,14 +1313,10 @@ RubberBandStretcher::Impl::process(const float *const *input, size_t samples, bo } #endif - if (m_debugLevel > 1) { - if (!allConsumed) cerr << "process looping" << endl; - } + m_log.log(2, "process looping"); } - if (m_debugLevel > 1) { - cerr << "process returning" << endl; - } + m_log.log(2, "process returning"); if (final) m_mode = Finished; } diff --git a/src/StretcherImpl.h b/src/faster/R2Stretcher.h similarity index 83% rename from src/StretcherImpl.h rename to src/faster/R2Stretcher.h index ceb5f12..b9e1709 100644 --- a/src/StretcherImpl.h +++ b/src/faster/R2Stretcher.h @@ -24,42 +24,36 @@ #ifndef RUBBERBAND_STRETCHERIMPL_H #define RUBBERBAND_STRETCHERIMPL_H -#include "../rubberband/RubberBandStretcher.h" +#include "../../rubberband/RubberBandStretcher.h" -#include "dsp/Window.h" -#include "dsp/SincWindow.h" -#include "dsp/FFT.h" +#include "../common/Window.h" +#include "../common/FFT.h" +#include "../common/RingBuffer.h" +#include "../common/Scavenger.h" +#include "../common/Thread.h" +#include "../common/Log.h" +#include "../common/sysutils.h" -#include "audiocurves/CompoundAudioCurve.h" - -#include "base/RingBuffer.h" -#include "base/Scavenger.h" -#include "system/Thread.h" -#include "system/sysutils.h" +#include "SincWindow.h" +#include "CompoundAudioCurve.h" #include #include -using namespace RubberBand; - namespace RubberBand { -#ifdef PROCESS_SAMPLE_TYPE -typedef PROCESS_SAMPLE_TYPE process_t; -#else -typedef double process_t; -#endif - class AudioCurveCalculator; class StretchCalculator; -class RubberBandStretcher::Impl +class R2Stretcher { public: - Impl(size_t sampleRate, size_t channels, Options options, - double initialTimeRatio, double initialPitchScale); - ~Impl(); + R2Stretcher(size_t sampleRate, size_t channels, + RubberBandStretcher::Options options, + double initialTimeRatio, double initialPitchScale, + Log log); + ~R2Stretcher(); void reset(); void setTimeRatio(double ratio); @@ -70,11 +64,11 @@ public: size_t getLatency() const; - void setTransientsOption(Options); - void setDetectorOption(Options); - void setPhaseOption(Options); - void setFormantOption(Options); - void setPitchOption(Options); + void setTransientsOption(RubberBandStretcher::Options); + void setDetectorOption(RubberBandStretcher::Options); + void setPhaseOption(RubberBandStretcher::Options); + void setFormantOption(RubberBandStretcher::Options); + void setPitchOption(RubberBandStretcher::Options); void setExpectedInputDuration(size_t samples); void setMaxProcessSize(size_t samples); @@ -106,7 +100,6 @@ public: void calculateStretch(); void setDebugLevel(int level); - static void setDefaultDebugLevel(int level) { m_defaultDebugLevel = level; } protected: size_t m_sampleRate; @@ -181,8 +174,8 @@ protected: #endif bool m_realtime; - Options m_options; - int m_debugLevel; + RubberBandStretcher::Options m_options; + Log m_log; enum ProcessMode { JustCreated, @@ -206,12 +199,13 @@ protected: class ProcessThread : public Thread { public: - ProcessThread(Impl *s, size_t c); + ProcessThread(R2Stretcher *s, size_t c); void run(); void signalDataAvailable(); void abandon(); + size_t channel() { return m_channel; } private: - Impl *m_s; + R2Stretcher *m_s; size_t m_channel; Condition m_dataAvailable; bool m_abandoning; @@ -220,19 +214,19 @@ protected: mutable Mutex m_threadSetMutex; typedef std::set ThreadSet; ThreadSet m_threadSet; - -#if defined HAVE_IPP && !defined USE_SPEEX + +#if defined(HAVE_IPP) && !defined(NO_THREADING) && !defined(USE_BQRESAMPLER) && !defined(USE_SPEEX) && !defined(HAVE_LIBSAMPLERATE) // Exasperatingly, the IPP polyphase resampler does not appear to - // be thread-safe as advertised -- a good reason to prefer the - // Speex alternative + // be thread-safe as advertised -- a good reason to prefer any of + // the alternatives +#define STRETCHER_IMPL_RESAMPLER_MUTEX_REQUIRED 1 Mutex m_resamplerMutex; #endif -#endif - +#endif // ! NO_THREADING + size_t m_inputDuration; CompoundAudioCurve::Type m_detectorType; std::vector m_phaseResetDf; - std::vector m_stretchDf; std::vector m_silence; int m_silentHistory; @@ -246,7 +240,6 @@ protected: Scavenger > m_emergencyScavenger; CompoundAudioCurve *m_phaseResetAudioCurve; - AudioCurveCalculator *m_stretchAudioCurve; AudioCurveCalculator *m_silentAudioCurve; StretchCalculator *m_stretchCalculator; @@ -260,7 +253,6 @@ protected: void writeOutput(RingBuffer &to, float *from, size_t qty, size_t &outCount, size_t theoreticalOut); - static int m_defaultDebugLevel; static const size_t m_defaultIncrement; static const size_t m_defaultFftSize; }; diff --git a/src/audiocurves/SilentAudioCurve.cpp b/src/faster/SilentAudioCurve.cpp similarity index 100% rename from src/audiocurves/SilentAudioCurve.cpp rename to src/faster/SilentAudioCurve.cpp diff --git a/src/audiocurves/SilentAudioCurve.h b/src/faster/SilentAudioCurve.h similarity index 97% rename from src/audiocurves/SilentAudioCurve.h rename to src/faster/SilentAudioCurve.h index b2cf090..69bab67 100644 --- a/src/audiocurves/SilentAudioCurve.h +++ b/src/faster/SilentAudioCurve.h @@ -24,7 +24,7 @@ #ifndef RUBBERBAND_SILENT_AUDIO_CURVE_H #define RUBBERBAND_SILENT_AUDIO_CURVE_H -#include "../dsp/AudioCurveCalculator.h" +#include "AudioCurveCalculator.h" namespace RubberBand { diff --git a/src/dsp/SincWindow.h b/src/faster/SincWindow.h similarity index 97% rename from src/dsp/SincWindow.h rename to src/faster/SincWindow.h index 6381f9d..b0441c0 100644 --- a/src/dsp/SincWindow.h +++ b/src/faster/SincWindow.h @@ -29,9 +29,9 @@ #include #include -#include "../system/sysutils.h" -#include "../system/VectorOps.h" -#include "../system/Allocators.h" +#include "../common/sysutils.h" +#include "../common/VectorOps.h" +#include "../common/Allocators.h" namespace RubberBand { diff --git a/src/StretcherChannelData.cpp b/src/faster/StretcherChannelData.cpp similarity index 81% rename from src/StretcherChannelData.cpp rename to src/faster/StretcherChannelData.cpp index 1c555dc..81c47d2 100644 --- a/src/StretcherChannelData.cpp +++ b/src/faster/StretcherChannelData.cpp @@ -23,42 +23,39 @@ #include "StretcherChannelData.h" -#include "dsp/Resampler.h" - -#include "system/Allocators.h" +#include "../common/Resampler.h" +#include "../common/Allocators.h" #include namespace RubberBand { -RubberBandStretcher::Impl::ChannelData::ChannelData(size_t windowSize, - size_t fftSize, - size_t outbufSize) +R2Stretcher::ChannelData::ChannelData(size_t windowSize, + size_t fftSize, + size_t outbufSize) { std::set s; construct(s, windowSize, fftSize, outbufSize); } -RubberBandStretcher::Impl::ChannelData::ChannelData(const std::set &sizes, - size_t initialWindowSize, - size_t initialFftSize, - size_t outbufSize) +R2Stretcher::ChannelData::ChannelData(const std::set &sizes, + size_t initialWindowSize, + size_t initialFftSize, + size_t outbufSize) { construct(sizes, initialWindowSize, initialFftSize, outbufSize); } void -RubberBandStretcher::Impl::ChannelData::construct(const std::set &sizes, - size_t initialWindowSize, - size_t initialFftSize, - size_t outbufSize) +R2Stretcher::ChannelData::construct(const std::set &sizes, + size_t initialWindowSize, + size_t initialFftSize, + size_t outbufSize) { size_t maxSize = initialWindowSize * 2; if (initialFftSize > maxSize) maxSize = initialFftSize; -// std::cerr << "ChannelData::construct: initialWindowSize = " << initialWindowSize << ", initialFftSize = " << initialFftSize << ", outbufSize = " << outbufSize << std::endl; - // std::set is ordered by value std::set::const_iterator i = sizes.end(); if (i != sizes.begin()) { @@ -69,8 +66,6 @@ RubberBandStretcher::Impl::ChannelData::construct(const std::set &sizes, // max possible size of the real "half" of freq data size_t realSize = maxSize / 2 + 1; -// std::cerr << "ChannelData::construct([" << sizes.size() << "], " << maxSize << ", " << realSize << ", " << outbufSize << ")" << std::endl; - if (outbufSize < maxSize) outbufSize = maxSize; inbuf = new RingBuffer(maxSize); @@ -115,11 +110,9 @@ RubberBandStretcher::Impl::ChannelData::construct(const std::set &sizes, void -RubberBandStretcher::Impl::ChannelData::setSizes(size_t windowSize, +R2Stretcher::ChannelData::setSizes(size_t windowSize, size_t fftSize) { -// std::cerr << "ChannelData::setSizes: windowSize = " << windowSize << ", fftSize = " << fftSize << std::endl; - size_t maxSize = 2 * std::max(windowSize, fftSize); size_t realSize = maxSize / 2 + 1; size_t oldMax = inbuf->getSize(); @@ -207,12 +200,10 @@ RubberBandStretcher::Impl::ChannelData::setSizes(size_t windowSize, } void -RubberBandStretcher::Impl::ChannelData::setOutbufSize(size_t outbufSize) +R2Stretcher::ChannelData::setOutbufSize(size_t outbufSize) { size_t oldSize = outbuf->getSize(); -// std::cerr << "ChannelData::setOutbufSize(" << outbufSize << ") [from " << oldSize << "]" << std::endl; - if (oldSize < outbufSize) { //!!! at this point we need a lock in case a different client @@ -225,13 +216,13 @@ RubberBandStretcher::Impl::ChannelData::setOutbufSize(size_t outbufSize) } void -RubberBandStretcher::Impl::ChannelData::setResampleBufSize(size_t sz) +R2Stretcher::ChannelData::setResampleBufSize(size_t sz) { resamplebuf = reallocate_and_zero(resamplebuf, resamplebufSize, sz); resamplebufSize = sz; } -RubberBandStretcher::Impl::ChannelData::~ChannelData() +R2Stretcher::ChannelData::~ChannelData() { delete resampler; @@ -260,7 +251,7 @@ RubberBandStretcher::Impl::ChannelData::~ChannelData() } void -RubberBandStretcher::Impl::ChannelData::reset() +R2Stretcher::ChannelData::reset() { inbuf->reset(); outbuf->reset(); diff --git a/src/StretcherChannelData.h b/src/faster/StretcherChannelData.h similarity index 98% rename from src/StretcherChannelData.h rename to src/faster/StretcherChannelData.h index ab23f41..1b2d673 100644 --- a/src/StretcherChannelData.h +++ b/src/faster/StretcherChannelData.h @@ -24,7 +24,7 @@ #ifndef RUBBERBAND_STRETCHERCHANNELDATA_H #define RUBBERBAND_STRETCHERCHANNELDATA_H -#include "StretcherImpl.h" +#include "R2Stretcher.h" #include #include @@ -34,7 +34,7 @@ namespace RubberBand class Resampler; -class RubberBandStretcher::Impl::ChannelData +class R2Stretcher::ChannelData { public: /** diff --git a/src/StretcherProcess.cpp b/src/faster/StretcherProcess.cpp similarity index 75% rename from src/StretcherProcess.cpp rename to src/faster/StretcherProcess.cpp index edc18c9..7044c7a 100644 --- a/src/StretcherProcess.cpp +++ b/src/faster/StretcherProcess.cpp @@ -21,19 +21,15 @@ you must obtain a valid commercial licence before doing so. */ -#include "StretcherImpl.h" - -#include "audiocurves/PercussiveAudioCurve.h" -#include "audiocurves/HighFrequencyAudioCurve.h" -#include "audiocurves/ConstantAudioCurve.h" - -#include "StretchCalculator.h" +#include "R2Stretcher.h" #include "StretcherChannelData.h" -#include "dsp/Resampler.h" -#include "base/Profiler.h" -#include "system/VectorOps.h" -#include "system/sysutils.h" +#include "../common/StretchCalculator.h" +#include "../common/Resampler.h" +#include "../common/Profiler.h" +#include "../common/VectorOps.h" +#include "../common/sysutils.h" +#include "../common/mathmisc.h" #include #include @@ -44,14 +40,11 @@ using namespace RubberBand; -using std::cerr; -using std::endl; - namespace RubberBand { #ifndef NO_THREADING -RubberBandStretcher::Impl::ProcessThread::ProcessThread(Impl *s, size_t c) : +R2Stretcher::ProcessThread::ProcessThread(R2Stretcher *s, size_t c) : m_s(s), m_channel(c), m_dataAvailable(std::string("data ") + char('A' + c)), @@ -59,22 +52,15 @@ RubberBandStretcher::Impl::ProcessThread::ProcessThread(Impl *s, size_t c) : { } void -RubberBandStretcher::Impl::ProcessThread::run() +R2Stretcher::ProcessThread::run() { - if (m_s->m_debugLevel > 1) { - cerr << "thread " << m_channel << " getting going" << endl; - } + m_s->m_log.log(2, "thread getting going for channel", m_channel); ChannelData &cd = *m_s->m_channelData[m_channel]; while (cd.inputSize == -1 || cd.inbuf->getReadSpace() > 0) { -// if (cd.inputSize != -1) { -// cerr << "inputSize == " << cd.inputSize -// << ", readSpace == " << cd.inbuf->getReadSpace() << endl; -// } - bool any = false, last = false; m_s->processChunks(m_channel, any, last); @@ -93,9 +79,7 @@ RubberBandStretcher::Impl::ProcessThread::run() m_dataAvailable.unlock(); if (m_abandoning) { - if (m_s->m_debugLevel > 1) { - cerr << "thread " << m_channel << " abandoning" << endl; - } + m_s->m_log.log(2, "thread abandoning for channel", m_channel); return; } } @@ -106,13 +90,11 @@ RubberBandStretcher::Impl::ProcessThread::run() m_s->m_spaceAvailable.signal(); m_s->m_spaceAvailable.unlock(); - if (m_s->m_debugLevel > 1) { - cerr << "thread " << m_channel << " done" << endl; - } + m_s->m_log.log(2, "thread done for channel", m_channel); } void -RubberBandStretcher::Impl::ProcessThread::signalDataAvailable() +R2Stretcher::ProcessThread::signalDataAvailable() { m_dataAvailable.lock(); m_dataAvailable.signal(); @@ -120,7 +102,7 @@ RubberBandStretcher::Impl::ProcessThread::signalDataAvailable() } void -RubberBandStretcher::Impl::ProcessThread::abandon() +R2Stretcher::ProcessThread::abandon() { m_abandoning = true; } @@ -128,16 +110,16 @@ RubberBandStretcher::Impl::ProcessThread::abandon() #endif bool -RubberBandStretcher::Impl::resampleBeforeStretching() const +R2Stretcher::resampleBeforeStretching() const { // We can't resample before stretching in offline mode, because // the stretch calculation is based on doing it the other way // around. It would take more work (and testing) to enable this. if (!m_realtime) return false; - if (m_options & OptionPitchHighQuality) { + if (m_options & RubberBandStretcher::OptionPitchHighQuality) { return (m_pitchScale < 1.0); // better sound - } else if (m_options & OptionPitchHighConsistency) { + } else if (m_options & RubberBandStretcher::OptionPitchHighConsistency) { return false; } else { return (m_pitchScale > 1.0); // better performance @@ -145,7 +127,7 @@ RubberBandStretcher::Impl::resampleBeforeStretching() const } void -RubberBandStretcher::Impl::prepareChannelMS(size_t c, +R2Stretcher::prepareChannelMS(size_t c, const float *const *inputs, size_t offset, size_t samples, @@ -165,13 +147,13 @@ RubberBandStretcher::Impl::prepareChannelMS(size_t c, } size_t -RubberBandStretcher::Impl::consumeChannel(size_t c, +R2Stretcher::consumeChannel(size_t c, const float *const *inputs, size_t offset, size_t samples, bool final) { - Profiler profiler("RubberBandStretcher::Impl::consumeChannel"); + Profiler profiler("R2Stretcher::consumeChannel"); ChannelData &cd = *m_channelData[c]; RingBuffer &inbuf = *cd.inbuf; @@ -183,13 +165,14 @@ RubberBandStretcher::Impl::consumeChannel(size_t c, const float *input = 0; - bool useMidSide = ((m_options & OptionChannelsTogether) && - (m_channels >= 2) && - (c < 2)); + bool useMidSide = + ((m_options & RubberBandStretcher::OptionChannelsTogether) && + (m_channels >= 2) && + (c < 2)); if (resampling) { - Profiler profiler2("RubberBandStretcher::Impl::resample"); + Profiler profiler2("R2Stretcher::resample"); toWrite = int(ceil(samples / m_pitchScale)); if (writable < toWrite) { @@ -208,17 +191,14 @@ RubberBandStretcher::Impl::consumeChannel(size_t c, size_t reqSize = int(ceil(samples / m_pitchScale)); if (reqSize > cd.resamplebufSize) { - cerr << "WARNING: RubberBandStretcher::Impl::consumeChannel: resizing resampler buffer from " - << cd.resamplebufSize << " to " << reqSize << endl; + m_log.log(0, "WARNING: R2Stretcher::consumeChannel: resizing resampler buffer from and to", cd.resamplebufSize, reqSize); cd.setResampleBufSize(reqSize); } -#ifndef NO_THREADING -#if defined HAVE_IPP && !defined USE_SPEEX +#if defined(STRETCHER_IMPL_RESAMPLER_MUTEX_REQUIRED) if (m_threaded) { m_resamplerMutex.lock(); } -#endif #endif if (useMidSide) { @@ -235,12 +215,10 @@ RubberBandStretcher::Impl::consumeChannel(size_t c, 1.0 / m_pitchScale, final); -#ifndef NO_THREADING -#if defined HAVE_IPP && !defined USE_SPEEX +#if defined(STRETCHER_IMPL_RESAMPLER_MUTEX_REQUIRED) if (m_threaded) { m_resamplerMutex.unlock(); } -#endif #endif } @@ -273,9 +251,9 @@ RubberBandStretcher::Impl::consumeChannel(size_t c, } void -RubberBandStretcher::Impl::processChunks(size_t c, bool &any, bool &last) +R2Stretcher::processChunks(size_t c, bool &any, bool &last) { - Profiler profiler("RubberBandStretcher::Impl::processChunks"); + Profiler profiler("R2Stretcher::processChunks"); // Process as many chunks as there are available on the input // buffer for channel c. This requires that the increments have @@ -293,9 +271,7 @@ RubberBandStretcher::Impl::processChunks(size_t c, bool &any, bool &last) while (!last) { if (!testInbufReadSpace(c)) { - if (m_debugLevel > 1) { - cerr << "processChunks: out of input" << endl; - } + m_log.log(2, "processChunks: out of input"); break; } @@ -318,9 +294,7 @@ RubberBandStretcher::Impl::processChunks(size_t c, bool &any, bool &last) (c, phaseIncrement, shiftIncrement, phaseReset); } else { size_t bit = m_aWindowSize/4; - if (m_debugLevel > 1) { - cerr << "channel " << c << " breaking down overlong increment " << shiftIncrement << " into " << bit << "-size bits" << endl; - } + m_log.log(2, "breaking down overlong increment into chunks from and to", shiftIncrement, bit); if (!tmp) tmp = allocate(m_aWindowSize); analyseChunk(c); v_copy(tmp, cd.fltbuf, m_aWindowSize); @@ -337,18 +311,17 @@ RubberBandStretcher::Impl::processChunks(size_t c, bool &any, bool &last) } cd.chunkCount++; - if (m_debugLevel > 2) { - cerr << "channel " << c << ": last = " << last << ", chunkCount = " << cd.chunkCount << endl; - } + m_log.log(3, "channel/last", c, last); + m_log.log(3, "channel/chunkCount", c, cd.chunkCount); } if (tmp) deallocate(tmp); } bool -RubberBandStretcher::Impl::processOneChunk() +R2Stretcher::processOneChunk() { - Profiler profiler("RubberBandStretcher::Impl::processOneChunk"); + Profiler profiler("R2Stretcher::processOneChunk"); // Process a single chunk for all channels, provided there is // enough data on each channel for at least one chunk. This is @@ -358,9 +331,7 @@ RubberBandStretcher::Impl::processOneChunk() for (size_t c = 0; c < m_channels; ++c) { if (!testInbufReadSpace(c)) { - if (m_debugLevel > 1) { - cerr << "processOneChunk: out of input" << endl; - } + m_log.log(2, "processOneChunk: out of input"); return false; } ChannelData &cd = *m_channelData[c]; @@ -389,9 +360,9 @@ RubberBandStretcher::Impl::processOneChunk() } bool -RubberBandStretcher::Impl::testInbufReadSpace(size_t c) +R2Stretcher::testInbufReadSpace(size_t c) { - Profiler profiler("RubberBandStretcher::Impl::testInbufReadSpace"); + Profiler profiler("R2Stretcher::testInbufReadSpace"); ChannelData &cd = *m_channelData[c]; RingBuffer &inbuf = *cd.inbuf; @@ -412,11 +383,7 @@ RubberBandStretcher::Impl::testInbufReadSpace(size_t c) #ifndef NO_THREADING if (!m_threaded) { #endif - if (m_debugLevel > 1) { - cerr << "Note: RubberBandStretcher: read space < chunk size (" - << inbuf.getReadSpace() << " < " << m_aWindowSize - << ") when not all input written, on processChunks for channel " << c << endl; - } + m_log.log(2, "Note: read space < chunk size when not all input written", inbuf.getReadSpace(), m_aWindowSize); #ifndef NO_THREADING } @@ -425,18 +392,10 @@ RubberBandStretcher::Impl::testInbufReadSpace(size_t c) } if (rs == 0) { - - if (m_debugLevel > 1) { - cerr << "read space = 0, giving up" << endl; - } + m_log.log(2, "read space = 0, giving up"); return false; - } else if (rs < m_aWindowSize/2) { - - if (m_debugLevel > 1) { - cerr << "read space = " << rs << ", setting draining true" << endl; - } - + m_log.log(2, "setting draining true with read space", rs); cd.draining = true; } } @@ -445,21 +404,21 @@ RubberBandStretcher::Impl::testInbufReadSpace(size_t c) } bool -RubberBandStretcher::Impl::processChunkForChannel(size_t c, +R2Stretcher::processChunkForChannel(size_t c, size_t phaseIncrement, size_t shiftIncrement, bool phaseReset) { - Profiler profiler("RubberBandStretcher::Impl::processChunkForChannel"); + Profiler profiler("R2Stretcher::processChunkForChannel"); // Process a single chunk on a single channel. This assumes // enough input data is available; caller must have tested this // using e.g. testInbufReadSpace first. Return true if this is // the last chunk on the channel. - if (phaseReset && (m_debugLevel > 1)) { - cerr << "processChunkForChannel: phase reset found, incrs " - << phaseIncrement << ":" << shiftIncrement << endl; + if (phaseReset) { + m_log.log(2, "processChunkForChannel: phase reset found, increments", + phaseIncrement, shiftIncrement); } ChannelData &cd = *m_channelData[c]; @@ -482,7 +441,7 @@ RubberBandStretcher::Impl::processChunkForChannel(size_t c, modifyChunk(c, phaseIncrement, phaseReset); synthesiseChunk(c, shiftIncrement); // reads from cd.mag, cd.phase - if (m_debugLevel > 2) { + if (m_log.getDebugLevel() > 2) { if (phaseReset) { for (int i = 0; i < 10; ++i) { cd.accumulator[i] = 1.2f - (i % 3) * 1.2f; @@ -494,19 +453,13 @@ RubberBandStretcher::Impl::processChunkForChannel(size_t c, bool last = false; if (cd.draining) { - if (m_debugLevel > 1) { - cerr << "draining: accumulator fill = " << cd.accumulatorFill << " (shiftIncrement = " << shiftIncrement << ")" << endl; - } + m_log.log(2, "draining: accumulator fill and shift increment", cd.accumulatorFill, shiftIncrement); if (shiftIncrement == 0) { - cerr << "WARNING: draining: shiftIncrement == 0, can't handle that in this context: setting to " << m_increment << endl; + m_log.log(0, "WARNING: draining: shiftIncrement == 0, can't handle that in this context: setting to", m_increment); shiftIncrement = m_increment; } if (cd.accumulatorFill <= shiftIncrement) { - if (m_debugLevel > 1) { - cerr << "reducing shift increment from " << shiftIncrement - << " to " << cd.accumulatorFill - << " and marking as last" << endl; - } + m_log.log(2, "draining: marking as last and reducing shift increment from and to", shiftIncrement, cd.accumulatorFill); shiftIncrement = cd.accumulatorFill; last = true; } @@ -520,9 +473,7 @@ RubberBandStretcher::Impl::processChunkForChannel(size_t c, int ws = cd.outbuf->getWriteSpace(); if (ws < required) { - if (m_debugLevel > 0) { - cerr << "Buffer overrun on output for channel " << c << endl; - } + m_log.log(1, "Buffer overrun on output for channel", c); // The only correct thing we can do here is resize the buffer. // We can't wait for the client thread to read some data out @@ -535,12 +486,10 @@ RubberBandStretcher::Impl::processChunkForChannel(size_t c, RingBuffer *oldbuf = cd.outbuf; cd.outbuf = oldbuf->resized(oldbuf->getSize() * 2); - if (m_debugLevel > 1) { - cerr << "(Write space was " << ws << ", needed " << required - << ": resized output buffer from " << oldbuf->getSize() - << " to " << cd.outbuf->getSize() << ")" << endl; - } - + m_log.log(2, "write space and space needed", ws, required); + m_log.log(2, "resized output buffer from and to", oldbuf->getSize(), + cd.outbuf->getSize()); + m_emergencyScavenger.claim(oldbuf); } @@ -549,14 +498,12 @@ RubberBandStretcher::Impl::processChunkForChannel(size_t c, } void -RubberBandStretcher::Impl::calculateIncrements(size_t &phaseIncrementRtn, +R2Stretcher::calculateIncrements(size_t &phaseIncrementRtn, size_t &shiftIncrementRtn, bool &phaseReset) { - Profiler profiler("RubberBandStretcher::Impl::calculateIncrements"); + Profiler profiler("R2Stretcher::calculateIncrements"); -// cerr << "calculateIncrements" << endl; - // Calculate the next upcoming phase and shift increment, on the // basis that both channels are in sync. This is in contrast to // getIncrements, which requires that all the increments have been @@ -579,7 +526,7 @@ RubberBandStretcher::Impl::calculateIncrements(size_t &phaseIncrementRtn, size_t bc = cd.chunkCount; for (size_t c = 1; c < m_channels; ++c) { if (m_channelData[c]->chunkCount != bc) { - cerr << "ERROR: RubberBandStretcher::Impl::calculateIncrements: Channels are not in sync" << endl; + m_log.log(0, "ERROR: R2Stretcher::calculateIncrements: Channels are not in sync"); return; } } @@ -634,7 +581,7 @@ RubberBandStretcher::Impl::calculateIncrements(size_t &phaseIncrementRtn, int incr = m_stretchCalculator->calculateSingle (m_timeRatio, effectivePitchRatio, df, m_increment, - m_aWindowSize, m_sWindowSize); + m_aWindowSize, m_sWindowSize, false); if (m_lastProcessPhaseResetDf.getWriteSpace() > 0) { m_lastProcessPhaseResetDf.write(&df, 1); @@ -676,20 +623,17 @@ RubberBandStretcher::Impl::calculateIncrements(size_t &phaseIncrementRtn, if (m_silentHistory >= int(m_aWindowSize / m_increment) && !phaseReset) { phaseReset = true; - if (m_debugLevel > 1) { - cerr << "calculateIncrements: phase reset on silence (silent history == " - << m_silentHistory << ")" << endl; - } + m_log.log(2, "calculateIncrements: phase reset on silence: silent history", m_silentHistory); } } bool -RubberBandStretcher::Impl::getIncrements(size_t channel, +R2Stretcher::getIncrements(size_t channel, size_t &phaseIncrementRtn, size_t &shiftIncrementRtn, bool &phaseReset) { - Profiler profiler("RubberBandStretcher::Impl::getIncrements"); + Profiler profiler("R2Stretcher::getIncrements"); if (channel >= m_channels) { phaseIncrementRtn = m_increment; @@ -718,9 +662,6 @@ RubberBandStretcher::Impl::getIncrements(size_t channel, bool gotData = true; if (cd.chunkCount >= m_outputIncrements.size()) { -// cerr << "WARNING: RubberBandStretcher::Impl::getIncrements:" -// << " chunk count " << cd.chunkCount << " >= " -// << m_outputIncrements.size() << endl; if (m_outputIncrements.size() == 0) { phaseIncrementRtn = m_increment; shiftIncrementRtn = m_increment; @@ -747,12 +688,12 @@ RubberBandStretcher::Impl::getIncrements(size_t channel, if (shiftIncrement < 0) { shiftIncrement = -shiftIncrement; } - /* - if (shiftIncrement >= int(m_windowSize)) { - cerr << "*** ERROR: RubberBandStretcher::Impl::processChunks: shiftIncrement " << shiftIncrement << " >= windowSize " << m_windowSize << " at " << cd.chunkCount << " (of " << m_outputIncrements.size() << ")" << endl; - shiftIncrement = m_windowSize; + + if (shiftIncrement >= int(m_aWindowSize)) { + m_log.log(1, "WARNING: shiftIncrement >= analysis window size", shiftIncrement, m_aWindowSize); + m_log.log(1, "at chunk of total", cd.chunkCount, m_outputIncrements.size()); } - */ + phaseIncrementRtn = phaseIncrement; shiftIncrementRtn = shiftIncrement; if (cd.chunkCount == 0) phaseReset = true; // don't mess with the first chunk @@ -760,9 +701,9 @@ RubberBandStretcher::Impl::getIncrements(size_t channel, } void -RubberBandStretcher::Impl::analyseChunk(size_t channel) +R2Stretcher::analyseChunk(size_t channel) { - Profiler profiler("RubberBandStretcher::Impl::analyseChunk"); + Profiler profiler("R2Stretcher::analyseChunk"); ChannelData &cd = *m_channelData[channel]; @@ -781,16 +722,16 @@ RubberBandStretcher::Impl::analyseChunk(size_t channel) } void -RubberBandStretcher::Impl::modifyChunk(size_t channel, +R2Stretcher::modifyChunk(size_t channel, size_t outputIncrement, bool phaseReset) { - Profiler profiler("RubberBandStretcher::Impl::modifyChunk"); + Profiler profiler("R2Stretcher::modifyChunk"); ChannelData &cd = *m_channelData[channel]; - if (phaseReset && m_debugLevel > 1) { - cerr << "phase reset: leaving phases unmodified" << endl; + if (phaseReset) { + m_log.log(2, "phase reset: leaving phases unmodified"); } const process_t rate = process_t(m_sampleRate); @@ -798,8 +739,8 @@ RubberBandStretcher::Impl::modifyChunk(size_t channel, bool unchanged = cd.unchanged && (outputIncrement == m_increment); bool fullReset = phaseReset; - bool laminar = !(m_options & OptionPhaseIndependent); - bool bandlimited = (m_options & OptionTransientsMixed); + bool laminar = !(m_options & RubberBandStretcher::OptionPhaseIndependent); + bool bandlimited = (m_options & RubberBandStretcher::OptionTransientsMixed); int bandlow = lrint((150 * m_fftSize) / rate); int bandhigh = lrint((1000 * m_fftSize) / rate); @@ -911,23 +852,21 @@ RubberBandStretcher::Impl::modifyChunk(size_t channel, cd.unwrappedPhase[i] = outphase; } - if (m_debugLevel > 2) { - cerr << "mean inheritance distance = " << distacc / count << endl; - } + m_log.log(3, "mean inheritance distance", distacc / count); if (fullReset) unchanged = true; cd.unchanged = unchanged; - if (unchanged && m_debugLevel > 1) { - cerr << "frame unchanged on channel " << channel << endl; + if (unchanged) { + m_log.log(2, "frame unchanged on channel", channel); } } void -RubberBandStretcher::Impl::formantShiftChunk(size_t channel) +R2Stretcher::formantShiftChunk(size_t channel) { - Profiler profiler("RubberBandStretcher::Impl::formantShiftChunk"); + Profiler profiler("R2Stretcher::formantShiftChunk"); ChannelData &cd = *m_channelData[channel]; @@ -943,8 +882,6 @@ RubberBandStretcher::Impl::formantShiftChunk(size_t channel) const int cutoff = m_sampleRate / 700; -// cerr <<"cutoff = "<< cutoff << ", m_sampleRate/cutoff = " << m_sampleRate/cutoff << endl; - dblbuf[0] /= 2; dblbuf[cutoff-1] /= 2; @@ -985,13 +922,12 @@ RubberBandStretcher::Impl::formantShiftChunk(size_t channel) } void -RubberBandStretcher::Impl::synthesiseChunk(size_t channel, +R2Stretcher::synthesiseChunk(size_t channel, size_t shiftIncrement) { - Profiler profiler("RubberBandStretcher::Impl::synthesiseChunk"); + Profiler profiler("R2Stretcher::synthesiseChunk"); - - if ((m_options & OptionFormantPreserved) && + if ((m_options & RubberBandStretcher::OptionFormantPreserved) && (m_pitchScale != 1.0)) { formantShiftChunk(channel); } @@ -1057,9 +993,9 @@ RubberBandStretcher::Impl::synthesiseChunk(size_t channel, } void -RubberBandStretcher::Impl::writeChunk(size_t channel, size_t shiftIncrement, bool last) +R2Stretcher::writeChunk(size_t channel, size_t shiftIncrement, bool last) { - Profiler profiler("RubberBandStretcher::Impl::writeChunk"); + Profiler profiler("R2Stretcher::writeChunk"); ChannelData &cd = *m_channelData[channel]; @@ -1069,8 +1005,10 @@ RubberBandStretcher::Impl::writeChunk(size_t channel, size_t shiftIncrement, boo const int sz = cd.accumulatorFill; const int si = shiftIncrement; - if (m_debugLevel > 2) { - cerr << "writeChunk(" << channel << ", " << shiftIncrement << ", " << last << ")" << endl; + m_log.log(3, "writeChunk: channel and shiftIncrement", + channel, shiftIncrement); + if (last) { + m_log.log(3, "writeChunk: last true"); } v_divide(accumulator, windowAccumulator, si); @@ -1085,10 +1023,11 @@ RubberBandStretcher::Impl::writeChunk(size_t channel, size_t shiftIncrement, boo bool resampledAlready = resampleBeforeStretching(); if (!resampledAlready && - (m_pitchScale != 1.0 || m_options & OptionPitchHighConsistency) && + (m_pitchScale != 1.0 || + (m_options & RubberBandStretcher::OptionPitchHighConsistency)) && cd.resampler) { - Profiler profiler2("RubberBandStretcher::Impl::resample"); + Profiler profiler2("R2Stretcher::resample"); size_t reqSize = int(ceil(si / m_pitchScale)); if (reqSize > cd.resamplebufSize) { @@ -1097,17 +1036,14 @@ RubberBandStretcher::Impl::writeChunk(size_t channel, size_t shiftIncrement, boo // first place. But we retain this check in case the // pitch scale has changed since then, or the stretch // calculator has gone mad, or something. - cerr << "WARNING: RubberBandStretcher::Impl::writeChunk: resizing resampler buffer from " - << cd.resamplebufSize << " to " << reqSize << endl; + m_log.log(0, "WARNING: R2Stretcher::writeChunk: resizing resampler buffer from and to", cd.resamplebufSize, reqSize); cd.setResampleBufSize(reqSize); } -#ifndef NO_THREADING -#if defined HAVE_IPP && !defined USE_SPEEX +#if defined(STRETCHER_IMPL_RESAMPLER_MUTEX_REQUIRED) if (m_threaded) { m_resamplerMutex.lock(); } -#endif #endif size_t outframes = cd.resampler->resample(&cd.resamplebuf, @@ -1117,12 +1053,10 @@ RubberBandStretcher::Impl::writeChunk(size_t channel, size_t shiftIncrement, boo 1.0 / m_pitchScale, last); -#ifndef NO_THREADING -#if defined HAVE_IPP && !defined USE_SPEEX +#if defined(STRETCHER_IMPL_RESAMPLER_MUTEX_REQUIRED) if (m_threaded) { m_resamplerMutex.unlock(); } -#endif #endif writeOutput(*cd.outbuf, cd.resamplebuf, @@ -1144,18 +1078,16 @@ RubberBandStretcher::Impl::writeChunk(size_t channel, size_t shiftIncrement, boo } else { cd.accumulatorFill = 0; if (cd.draining) { - if (m_debugLevel > 1) { - cerr << "RubberBandStretcher::Impl::processChunks: setting outputComplete to true" << endl; - } + m_log.log(2, "processChunks: setting outputComplete to true"); cd.outputComplete = true; } } } void -RubberBandStretcher::Impl::writeOutput(RingBuffer &to, float *from, size_t qty, size_t &outCount, size_t theoreticalOut) +R2Stretcher::writeOutput(RingBuffer &to, float *from, size_t qty, size_t &outCount, size_t theoreticalOut) { - Profiler profiler("RubberBandStretcher::Impl::writeOutput"); + Profiler profiler("R2Stretcher::writeOutput"); // In non-RT mode, we don't want to write the first startSkip // samples, because the first chunk is centred on the start of the @@ -1172,31 +1104,23 @@ RubberBandStretcher::Impl::writeOutput(RingBuffer &to, float *from, size_ // this is the normal case if (theoreticalOut > 0) { - if (m_debugLevel > 1) { - cerr << "theoreticalOut = " << theoreticalOut - << ", outCount = " << outCount - << ", startSkip = " << startSkip - << ", qty = " << qty << endl; - } + m_log.log(2, "theoreticalOut and outCount", + theoreticalOut, outCount); + m_log.log(2, "startSkip and qty", + startSkip, qty); if (outCount - startSkip <= theoreticalOut && outCount - startSkip + qty > theoreticalOut) { qty = theoreticalOut - (outCount - startSkip); - if (m_debugLevel > 1) { - cerr << "reduce qty to " << qty << endl; - } + m_log.log(2, "reducing qty to", qty); } } - if (m_debugLevel > 2) { - cerr << "writing " << qty << endl; - } + m_log.log(3, "writing", qty); size_t written = to.write(from, qty); if (written < qty) { - cerr << "WARNING: RubberBandStretcher::Impl::writeOutput: " - << "Buffer overrun on output: wrote " << written - << " of " << qty << " samples" << endl; + m_log.log(0, "WARNING: writeOutput: buffer overrun: wanted to write and able to write", qty, written); } outCount += written; @@ -1206,30 +1130,24 @@ RubberBandStretcher::Impl::writeOutput(RingBuffer &to, float *from, size_ // the rest of this is only used during the first startSkip samples if (outCount + qty <= startSkip) { - if (m_debugLevel > 1) { - cerr << "qty = " << qty << ", startSkip = " - << startSkip << ", outCount = " << outCount - << ", discarding" << endl; - } + m_log.log(2, "discarding with startSkip", startSkip); + m_log.log(2, "qty and outCount", qty, outCount); outCount += qty; return; } size_t off = startSkip - outCount; - if (m_debugLevel > 1) { - cerr << "qty = " << qty << ", startSkip = " - << startSkip << ", outCount = " << outCount - << ", writing " << qty - off - << " from start offset " << off << endl; - } + m_log.log(2, "shortening with startSkip", startSkip); + m_log.log(2, "qty and outCount", qty, outCount); + m_log.log(2, "start offset and number written", off, qty - off); to.write(from + off, qty - off); outCount += qty; } int -RubberBandStretcher::Impl::available() const +R2Stretcher::available() const { - Profiler profiler("RubberBandStretcher::Impl::available"); + Profiler profiler("R2Stretcher::available"); #ifndef NO_THREADING if (m_threaded) { @@ -1245,15 +1163,12 @@ RubberBandStretcher::Impl::available() const #endif for (size_t c = 0; c < m_channels; ++c) { if (m_channelData[c]->inputSize >= 0) { -// cerr << "available: m_done true" << endl; if (m_channelData[c]->inbuf->getReadSpace() > 0) { - if (m_debugLevel > 1) { - cerr << "calling processChunks(" << c << ") from available" << endl; - } + m_log.log(2, "calling processChunks from available, channel" , c); //!!! do we ever actually do this? if so, this method should not be const // ^^^ yes, we do sometimes -- e.g. when fed a very short file bool any = false, last = false; - ((RubberBandStretcher::Impl *)this)->processChunks(c, any, last); + ((R2Stretcher *)this)->processChunks(c, any, last); } } } @@ -1268,9 +1183,7 @@ RubberBandStretcher::Impl::available() const for (size_t i = 0; i < m_channels; ++i) { size_t availIn = m_channelData[i]->inbuf->getReadSpace(); size_t availOut = m_channelData[i]->outbuf->getReadSpace(); - if (m_debugLevel > 2) { - cerr << "available on channel " << i << ": " << availOut << " (waiting: " << availIn << ")" << endl; - } + m_log.log(3, "available in and out", availIn, availOut); if (i == 0 || availOut < min) min = availOut; if (!m_channelData[i]->outputComplete) consumed = false; if (m_channelData[i]->resampler) haveResamplers = true; @@ -1284,9 +1197,9 @@ RubberBandStretcher::Impl::available() const } size_t -RubberBandStretcher::Impl::retrieve(float *const *output, size_t samples) const +R2Stretcher::retrieve(float *const *output, size_t samples) const { - Profiler profiler("RubberBandStretcher::Impl::retrieve"); + Profiler profiler("R2Stretcher::retrieve"); size_t got = samples; @@ -1294,15 +1207,14 @@ RubberBandStretcher::Impl::retrieve(float *const *output, size_t samples) const size_t gotHere = m_channelData[c]->outbuf->read(output[c], got); if (gotHere < got) { if (c > 0) { - if (m_debugLevel > 0) { - cerr << "RubberBandStretcher::Impl::retrieve: WARNING: channel imbalance detected" << endl; - } + m_log.log(0, "R2Stretcher::retrieve: WARNING: channel imbalance detected"); } got = gotHere; } } - if ((m_options & OptionChannelsTogether) && (m_channels >= 2)) { + if ((m_options & RubberBandStretcher::OptionChannelsTogether) && + (m_channels >= 2)) { for (size_t i = 0; i < got; ++i) { float mid = output[0][i]; float side = output[1][i]; diff --git a/src/finer/BinClassifier.h b/src/finer/BinClassifier.h new file mode 100644 index 0000000..80069a9 --- /dev/null +++ b/src/finer/BinClassifier.h @@ -0,0 +1,149 @@ +/* -*- 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-2022 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 RUBBERBAND_BIN_CLASSIFIER_H +#define RUBBERBAND_BIN_CLASSIFIER_H + +#include "../common/Allocators.h" +#include "../common/MovingMedian.h" +#include "../common/RingBuffer.h" + +#include +#include + +namespace RubberBand { + +class BinClassifier +{ +public: + enum class Classification { + Harmonic = 0, + Percussive = 1, + Residual = 2 + }; + + struct Parameters { + int binCount; + int horizontalFilterLength; + int horizontalFilterLag; + int verticalFilterLength; + double harmonicThreshold; + double percussiveThreshold; + Parameters(int _binCount, int _horizontalFilterLength, + int _horizontalFilterLag, int _verticalFilterLength, + double _harmonicThreshold, double _percussiveThreshold) : + binCount(_binCount), + horizontalFilterLength(_horizontalFilterLength), + horizontalFilterLag(_horizontalFilterLag), + verticalFilterLength(_verticalFilterLength), + harmonicThreshold(_harmonicThreshold), + percussiveThreshold(_percussiveThreshold) { } + }; + + BinClassifier(Parameters parameters) : + m_parameters(parameters), + m_hFilters(new MovingMedianStack(m_parameters.binCount, + m_parameters.horizontalFilterLength)), + m_vFilter(new MovingMedian(m_parameters.verticalFilterLength)), + m_vfQueue(parameters.horizontalFilterLag) + { + int n = m_parameters.binCount; + + m_hf = allocate_and_zero(n); + m_vf = allocate_and_zero(n); + + for (int i = 0; i < m_parameters.horizontalFilterLag; ++i) { + double *entry = allocate_and_zero(n); + m_vfQueue.write(&entry, 1); + } + } + + ~BinClassifier() + { + while (m_vfQueue.getReadSpace() > 0) { + double *entry = m_vfQueue.readOne(); + deallocate(entry); + } + + deallocate(m_hf); + deallocate(m_vf); + } + + void reset() + { + m_hFilters->reset(); + } + + void classify(const double *const mag, // input, of at least binCount bins + Classification *classification) // output, of binCount bins + { + const int n = m_parameters.binCount; + + for (int i = 0; i < n; ++i) { + m_hFilters->push(i, mag[i]); + m_hf[i] = m_hFilters->get(i); + } + + v_copy(m_vf, mag, n); + MovingMedian::filter(*m_vFilter, m_vf, n); + + if (m_parameters.horizontalFilterLag > 0) { + double *lagged = m_vfQueue.readOne(); + m_vfQueue.write(&m_vf, 1); + m_vf = lagged; + } + + double eps = 1.0e-7; + + for (int i = 0; i < n; ++i) { + Classification c; + if (double(m_hf[i]) / (double(m_vf[i]) + eps) > + m_parameters.harmonicThreshold) { + c = Classification::Harmonic; + } else if (double(m_vf[i]) / (double(m_hf[i]) + eps) > + m_parameters.percussiveThreshold) { + c = Classification::Percussive; + } else { + c = Classification::Residual; + } + classification[i] = c; + } + } + +protected: + Parameters m_parameters; + std::unique_ptr> m_hFilters; + std::unique_ptr> m_vFilter; + // We manage the queued frames through pointer swapping, hence + // bare pointers here + double *m_hf; + double *m_vf; + RingBuffer m_vfQueue; + + BinClassifier(const BinClassifier &) =delete; + BinClassifier &operator=(const BinClassifier &) =delete; +}; + +} + +#endif diff --git a/src/finer/BinSegmenter.h b/src/finer/BinSegmenter.h new file mode 100644 index 0000000..705d255 --- /dev/null +++ b/src/finer/BinSegmenter.h @@ -0,0 +1,146 @@ +/* -*- 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-2022 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 RUBBERBAND_BIN_SEGMENTER_H +#define RUBBERBAND_BIN_SEGMENTER_H + +#include "BinClassifier.h" + +#include "../common/HistogramFilter.h" +#include "../common/mathmisc.h" + +#include + +namespace RubberBand { + +class BinSegmenter +{ +public: + struct Segmentation { + double percussiveBelow; + double percussiveAbove; + double residualAbove; + explicit Segmentation() : + percussiveBelow(0.0), percussiveAbove(0.0), residualAbove(0.0) { } + Segmentation(double _pb, double _pa, double _ra) : + percussiveBelow(_pb), percussiveAbove(_pa), residualAbove(_ra) { } + }; + + struct Parameters { + int fftSize; + int binCount; + double sampleRate; + int classFilterLength; + Parameters(int _fftSize, int _binCount, double _sampleRate, + int _classFilterLength) : + fftSize(_fftSize), binCount(_binCount), sampleRate(_sampleRate), + classFilterLength(_classFilterLength) { } + }; + + BinSegmenter(Parameters parameters) : + m_parameters(parameters), + m_numeric(m_parameters.binCount, 0), + m_classFilter(3, m_parameters.classFilterLength) + { + } + + Segmentation segment(const BinClassifier::Classification *classification) { + int n = m_parameters.binCount; + for (int i = 0; i < n; ++i) { + switch (classification[i]) { + case BinClassifier::Classification::Harmonic: + m_numeric[i] = 0; break; + case BinClassifier::Classification::Percussive: + m_numeric[i] = 1; break; + default: + m_numeric[i] = 2; break; + } + } + HistogramFilter::modalFilter(m_classFilter, m_numeric); +/* + std::cout << "c:"; + for (int i = 0; i < n; ++i) { + if (i > 0) std::cout << ","; + std::cout << m_numeric[i]; + } + std::cout << std::endl; +*/ + double f0 = 0.0; + for (int i = 1; i < n; ++i) { + if (m_numeric[i] != 1) { // percussive + if (i == 1 && m_numeric[0] != 1) { // percussive + f0 = 0.0; + } else { + f0 = frequencyForBin + (i, m_parameters.fftSize, m_parameters.sampleRate); + } + break; + } + } + double nyquist = m_parameters.sampleRate / 2.0; + double f1 = nyquist; + double f2 = nyquist; + bool inPercussive = false; + for (int i = n - 1; i > 0; --i) { + int c = m_numeric[i]; + if (!inPercussive) { + if (c == 2) { // residual + continue; + } else if (c == 1) { // percussive + inPercussive = true; + f2 = frequencyForBin + (i, m_parameters.fftSize, m_parameters.sampleRate); + } else { // harmonic + f1 = f2 = frequencyForBin + (i, m_parameters.fftSize, m_parameters.sampleRate); + break; + } + } else { // inPercussive + if (c != 1) { // non-percussive + f1 = frequencyForBin + (i, m_parameters.fftSize, m_parameters.sampleRate); + break; + } + } + } + if (f1 == nyquist && f2 < nyquist) { + f1 = 0.0; + } + +// std::cout << "f0 = " << f0 << ", f1 = " << f1 << ", f2 = " << f2 << std::endl; + + return Segmentation(f0, f1, f2); + } + +protected: + Parameters m_parameters; + std::vector m_numeric; + HistogramFilter m_classFilter; + + BinSegmenter(const BinSegmenter &) =delete; + BinSegmenter &operator=(const BinSegmenter &) =delete; +}; + +} + +#endif diff --git a/src/finer/Guide.h b/src/finer/Guide.h new file mode 100644 index 0000000..8162b01 --- /dev/null +++ b/src/finer/Guide.h @@ -0,0 +1,495 @@ +/* -*- 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-2022 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 RUBBERBAND_GUIDE_H +#define RUBBERBAND_GUIDE_H + +#include "../common/Log.h" + +#include +#include + +namespace RubberBand +{ + +class Guide +{ +public: + struct FftBand { + int fftSize; + double f0; + double f1; + FftBand(int _s, double _f0, double _f1) : + fftSize(_s), f0(_f0), f1(_f1) { } + FftBand() : + fftSize(0), f0(0.f), f1(0.f) { } + }; + + struct PhaseLockBand { + int p; + double beta; + double f0; + double f1; + PhaseLockBand(int _p, double _beta, double _f0, double _f1) : + p(_p), beta(_beta), f0(_f0), f1(_f1) { } + PhaseLockBand() : + p(0), beta(1.0), f0(0.f), f1(0.f) { } + }; + + struct Range { + bool present; + double f0; + double f1; + Range(bool _present, double _f0, double _f1) : + present(_present), f0(_f0), f1(_f1) { } + Range() : + present(false), f0(0.f), f1(0.f) { } + }; + + struct Guidance { + FftBand fftBands[3]; + PhaseLockBand phaseLockBands[4]; + Range kick; + Range preKick; + Range highUnlocked; + Range phaseReset; + Range channelLock; + }; + + struct BandLimits { + int fftSize; + double f0min; + double f1max; + int b0min; + int b1max; + BandLimits(int _fftSize, double _rate, double _f0min, double _f1max) : + fftSize(_fftSize), f0min(_f0min), f1max(_f1max), + b0min(int(floor(f0min * fftSize / _rate))), + b1max(int(ceil(f1max * fftSize / _rate))) { } + BandLimits() : + fftSize(0), f0min(0.f), f1max(0.f), b0min(0), b1max(0) { } + }; + + struct Configuration { + int longestFftSize; + int shortestFftSize; + int classificationFftSize; + BandLimits fftBandLimits[3]; + Configuration(int _longestFftSize, int _shortestFftSize, + int _classificationFftSize) : + longestFftSize(_longestFftSize), + shortestFftSize(_shortestFftSize), + classificationFftSize(_classificationFftSize) { } + }; + + struct Parameters { + double sampleRate; + Parameters(double _sampleRate) : sampleRate(_sampleRate) { } + }; + + Guide(Parameters parameters, Log log) : + m_parameters(parameters), + m_log(log), + m_configuration(roundUp(int(ceil(parameters.sampleRate / 16.0))), + roundUp(int(ceil(parameters.sampleRate / 64.0))), + roundUp(int(ceil(parameters.sampleRate / 32.0)))), + m_minLower(500.0), m_minHigher(4000.0), + m_defaultLower(700.0), m_defaultHigher(4800.0), + m_maxLower(1100.0), m_maxHigher(7000.0) + { + double rate = m_parameters.sampleRate; + + m_log.log(1, "Guide: rate", rate); + + int bandFftSize = roundUp(int(ceil(rate/16.0))); + m_configuration.fftBandLimits[0] = + BandLimits(bandFftSize, rate, 0.0, m_maxLower); + + // This is the classification and fallback FFT: we need it to + // go up to Nyquist so we can seamlessly switch to it for + // longer stretches, and down to 0.0 so we can use it for + // unity in offline mode + bandFftSize = roundUp(int(ceil(rate/32.0))); + m_configuration.fftBandLimits[1] = + BandLimits(bandFftSize, rate, 0.0, rate / 2.0); + + bandFftSize = roundUp(int(ceil(rate/64.0))); + m_configuration.fftBandLimits[2] = + BandLimits(bandFftSize, rate, m_minHigher, rate/2.0); + + m_log.log(1, "Guide: classification FFT size", + m_configuration.classificationFftSize); + } + + const Configuration &getConfiguration() const { + return m_configuration; + } + + void updateGuidance(double ratio, + int outhop, + const double *const magnitudes, + const double *const prevMagnitudes, + const double *const nextMagnitudes, + const BinSegmenter::Segmentation &segmentation, + const BinSegmenter::Segmentation &prevSegmentation, + const BinSegmenter::Segmentation &nextSegmentation, + double meanMagnitude, + int unityCount, + bool realtime, + Guidance &guidance) const { + + bool hadPhaseReset = guidance.phaseReset.present; + + guidance.phaseReset.present = false; + guidance.kick.present = false; + guidance.preKick.present = false; + guidance.highUnlocked.present = false; + guidance.channelLock.present = false; + + double nyquist = m_parameters.sampleRate / 2.0; + guidance.fftBands[0].fftSize = roundUp(int(ceil(nyquist/8.0))); + guidance.fftBands[1].fftSize = roundUp(int(ceil(nyquist/16.0))); + guidance.fftBands[2].fftSize = roundUp(int(ceil(nyquist/32.0))); + + // This is a vital stop case for PhaseAdvance + guidance.phaseLockBands[3].f1 = nyquist; + + if (meanMagnitude < 1.0e-6) { + updateForSilence(guidance); + return; + } + + if (unityCount > 0) { + updateForUnity(guidance, + hadPhaseReset, + unityCount, + magnitudes, + segmentation, + realtime); + return; + } + + guidance.channelLock.present = true; + guidance.channelLock.f0 = 0.0; + guidance.channelLock.f1 = 600.0; + + bool kick = + (segmentation.percussiveBelow > 40.0) && + (prevSegmentation.percussiveBelow < 40.0) && + checkPotentialKick(magnitudes, prevMagnitudes); + + bool futureKick = !kick && + (nextSegmentation.percussiveBelow > 40.0) && + (segmentation.percussiveBelow < 40.0) && + checkPotentialKick(nextMagnitudes, magnitudes); +/* + std::cout << "d:" + << prevSegmentation.percussiveBelow << "," + << segmentation.percussiveBelow << "," + << nextSegmentation.percussiveBelow << "," + << checkPotentialKick(magnitudes, prevMagnitudes) << "," + << checkPotentialKick(nextMagnitudes, magnitudes) << "," + << (kick ? "K" : "N") << "," + << (futureKick ? "F" : "N") << std::endl; +*/ + if (kick) { + guidance.kick.present = true; + guidance.kick.f0 = 0.0; + guidance.kick.f1 = segmentation.percussiveBelow; + } else if (futureKick) { + guidance.preKick.present = true; + guidance.preKick.f0 = 0.0; + guidance.preKick.f1 = nextSegmentation.percussiveBelow; + } + + if (segmentation.residualAbove > segmentation.percussiveAbove) { + guidance.highUnlocked.present = true; + guidance.highUnlocked.f0 = segmentation.percussiveAbove; + guidance.highUnlocked.f1 = segmentation.residualAbove; + } + + double bigGap = 4000.0; + if (segmentation.residualAbove > + segmentation.percussiveAbove + bigGap && + prevSegmentation.residualAbove < + prevSegmentation.percussiveAbove + bigGap) { + guidance.phaseReset.present = true; + guidance.phaseReset.f0 = std::min(segmentation.percussiveAbove, + nextSegmentation.percussiveAbove); + guidance.phaseReset.f1 = std::max(segmentation.residualAbove, + nextSegmentation.residualAbove); + if (guidance.phaseReset.f0 < 200.0) { + guidance.phaseReset.f0 = 0.0; + } + } + + double prevLower = guidance.fftBands[0].f1; + double lower = descendToValley(prevLower, magnitudes); + if (lower > m_maxLower || lower < m_minLower) { + lower = m_defaultLower; + } + + double prevHigher = guidance.fftBands[1].f1; + double higher = descendToValley(prevHigher, magnitudes); + if (higher > m_maxHigher || higher < m_minHigher) { + higher = m_defaultHigher; + } + + guidance.fftBands[0].f0 = 0.0; + guidance.fftBands[0].f1 = lower; + +// std::cout << "x:" << lower << std::endl; + + guidance.fftBands[1].f0 = lower; + guidance.fftBands[1].f1 = higher; + + guidance.fftBands[2].f0 = higher; + guidance.fftBands[2].f1 = nyquist; + + if (outhop > 256) { + guidance.fftBands[1].f1 = nyquist; + guidance.fftBands[2].f0 = nyquist; + } + + double mid = std::max(lower, 1600.0); + + guidance.phaseLockBands[0].p = 1; + guidance.phaseLockBands[0].beta = betaFor(300.0, ratio); + guidance.phaseLockBands[0].f0 = 0.0; + guidance.phaseLockBands[0].f1 = lower; + + guidance.phaseLockBands[1].p = 2; + guidance.phaseLockBands[1].beta = betaFor(1600.0, ratio); + guidance.phaseLockBands[1].f0 = lower; + guidance.phaseLockBands[1].f1 = mid; + + guidance.phaseLockBands[2].p = 3; + guidance.phaseLockBands[2].beta = betaFor(5000.0, ratio); + guidance.phaseLockBands[2].f0 = mid; + guidance.phaseLockBands[2].f1 = higher; + + guidance.phaseLockBands[3].p = 4; + guidance.phaseLockBands[3].beta = betaFor(10000.0, ratio); + guidance.phaseLockBands[3].f0 = higher; + guidance.phaseLockBands[3].f1 = nyquist; + + if (outhop > 256) { + guidance.phaseLockBands[3].p = 3; + } + + if (ratio > 2.0) { + + // For very long stretches, diffuse is better than + // metallic - gradually unlock the higher frequencies and + // reduce the channel lock + + double channelLimit = guidance.channelLock.f1; + channelLimit = channelLimit - (ratio - 2.0) * 150.0; + if (channelLimit < 100.0) channelLimit = 100.0; + guidance.channelLock.f1 = channelLimit; + + double unlockedAbove = 12000.0 - (ratio - 2.0) * 400.0; + if (unlockedAbove < channelLimit) unlockedAbove = channelLimit; + if (guidance.highUnlocked.present) { + guidance.highUnlocked.f0 = std::min(guidance.highUnlocked.f0, + unlockedAbove); + } else { + guidance.highUnlocked.f0 = unlockedAbove; + } + guidance.highUnlocked.f1 = nyquist; + guidance.highUnlocked.present = true; + } + + /* + std::ostringstream str; + str << "Guidance: FFT bands: [" + << guidance.fftBands[0].fftSize << " from " + << guidance.fftBands[0].f0 << " to " << guidance.fftBands[0].f1 + << ", " + << guidance.fftBands[1].fftSize << " from " + << guidance.fftBands[1].f0 << " to " << guidance.fftBands[1].f1 + << ", " + << guidance.fftBands[2].fftSize << " from " + << guidance.fftBands[2].f0 << " to " << guidance.fftBands[2].f1 + << "]; phase reset range: [" + << guidance.phaseReset.present << " from " + << guidance.phaseReset.f0 << " to " << guidance.phaseReset.f1 + << "]" << std::endl; + m_parameters.logger(str.str()); + */ + } + + void setDebugLevel(int level) { + m_log.setDebugLevel(level); + } + +protected: + Parameters m_parameters; + Log m_log; + Configuration m_configuration; + + double m_minLower; + double m_minHigher; + double m_defaultLower; + double m_defaultHigher; + double m_maxLower; + double m_maxHigher; + + // near-dupe with R2 RubberBandStretcher::Impl + int roundUp(int value) const { + if (value < 1) return 1; + if (!(value & (value - 1))) return value; + int bits = 0; + while (value) { ++bits; value >>= 1; } + value = 1 << bits; + return value; + } + + void updateForSilence(Guidance &guidance) const { +// std::cout << "phase reset on silence" << std::endl; + double nyquist = m_parameters.sampleRate / 2.0; + guidance.fftBands[0].f0 = 0.0; + guidance.fftBands[0].f1 = 0.0; + guidance.fftBands[1].f0 = 0.0; + guidance.fftBands[1].f1 = nyquist; + guidance.fftBands[2].f0 = nyquist; + guidance.fftBands[2].f1 = nyquist; + guidance.phaseReset.present = true; + guidance.phaseReset.f0 = 0.0; + guidance.phaseReset.f1 = nyquist; + } + + void updateForUnity(Guidance &guidance, + bool hadPhaseReset, + uint32_t /* unityCount */, + const double *const /* magnitudes */, + const BinSegmenter::Segmentation &segmentation, + bool realtime) const { + +// std::cout << "unity" << std::endl; + + double nyquist = m_parameters.sampleRate / 2.0; + + if (!realtime) { + // ratio can't change, so we are just running 1.0 ratio + // throughout + guidance.fftBands[0].f0 = 0.0; + guidance.fftBands[0].f1 = 0.0; + guidance.fftBands[1].f0 = 0.0; + guidance.fftBands[1].f1 = nyquist; + guidance.fftBands[2].f0 = nyquist; + guidance.fftBands[2].f1 = nyquist; + guidance.phaseReset.present = true; + guidance.phaseReset.f0 = 0.0; + guidance.phaseReset.f1 = nyquist; + return; + } + + guidance.fftBands[0].f0 = 0.0; + guidance.fftBands[0].f1 = m_minLower; + guidance.fftBands[1].f0 = m_minLower; + guidance.fftBands[1].f1 = m_minHigher; + guidance.fftBands[2].f0 = m_minHigher; + guidance.fftBands[2].f1 = nyquist; + + guidance.phaseReset.present = true; + + if (!hadPhaseReset) { + guidance.phaseReset.f0 = 16000.0; + guidance.phaseReset.f1 = nyquist; +// std::cout << "f0 = " << guidance.phaseReset.f0 << std::endl; + return; + } else { + guidance.phaseReset.f0 *= 0.9; + guidance.phaseReset.f1 *= 1.1; + } + + if (guidance.phaseReset.f0 < segmentation.residualAbove) { + guidance.phaseReset.f0 = std::min(guidance.phaseReset.f0, + segmentation.percussiveAbove); + } + + if (guidance.phaseReset.f1 > 16000.0) { + guidance.phaseReset.f1 = nyquist; + } + + if (guidance.phaseReset.f0 < 100.0) { + guidance.phaseReset.f0 = 0.0; + } + +// if (guidance.phaseReset.f0 > 0.0) { +// std::cout << unityCount << ": f0 = " << guidance.phaseReset.f0 +// << ", f1 = " << guidance.phaseReset.f1 << std::endl; +// } + } + + bool checkPotentialKick(const double *const magnitudes, + const double *const prevMagnitudes) const { + int b = binForFrequency(200.0, m_configuration.classificationFftSize, + m_parameters.sampleRate); + double here = 0.0, there = 0.0; + for (int i = 1; i <= b; ++i) { + here += magnitudes[i]; + } + for (int i = 1; i <= b; ++i) { + there += prevMagnitudes[i]; + } + return (here > 10.e-3 && here > there * 1.4); + } + + double descendToValley(double f, const double *const magnitudes) const { + if (f == 0.0 || f == m_parameters.sampleRate/2.0) { + // These are special cases + return f; + } + int b = binForFrequency(f, m_configuration.classificationFftSize, + m_parameters.sampleRate); + int n = m_configuration.classificationFftSize/2; + for (int i = 0; i < 3; ++i) { + if (b < n && magnitudes[b+1] < magnitudes[b]) { + ++b; + } else if (b > 0 && magnitudes[b-1] < magnitudes[b]) { + --b; + } else { + break; + } + } + double sf = frequencyForBin(b, m_configuration.classificationFftSize, + m_parameters.sampleRate); + return sf; + } + + double betaFor(double f, double ratio) const { + double b = (2.0 + ratio) / 3.0; + double limit = 10000.0; + if (f > limit) { + return b; + } else { + return 1.0 + f * (b - 1.0) / limit; + } + } +}; + +} + +#endif diff --git a/src/finer/Peak.h b/src/finer/Peak.h new file mode 100644 index 0000000..7ede11c --- /dev/null +++ b/src/finer/Peak.h @@ -0,0 +1,137 @@ +/* -*- 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-2022 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 RUBBERBAND_PEAK_H +#define RUBBERBAND_PEAK_H + +#include + +namespace RubberBand +{ + +template > +class Peak +{ +public: + /** Peak picker for array of length n. This allocates on + construction an internal buffer for temporary values, to be + used within the peak-picking functions, so that it does not + have to allocate when used. It does not have persistent state. + */ + Peak(int n) : + m_n(n), + m_locations(n, 0) { } + + /** Find the nearest peak to each bin, and optionally the next + highest peak above each bin, within an array v, where a peak + is a value greater than the p nearest neighbours on each + side. The array must have length n where n is the size passed + the the constructor. + */ + void findNearestAndNextPeaks(const T *v, + int p, + int *nearest, + int *next = nullptr) + { + findNearestAndNextPeaks(v, 0, m_n, p, nearest, next); + } + + /** As above but consider only the range of size rangeCount from + index rangeStart. Write rangeCount results into nearest and + optionally next, starting to write at index rangeStart - so + these arrays must have the full length even if rangeCount is + shorter. Leave the rest of nearest and/or next unmodified. + */ + void findNearestAndNextPeaks(const T *v, + int rangeStart, + int rangeCount, + int p, + int *nearest, + int *next = nullptr) + { + int nPeaks = 0; + int n = rangeStart + rangeCount; + GreaterThan greater; + + for (int i = rangeStart; i < n; ++i) { + T x = v[i]; + bool good = true; + for (int k = i - p; k <= i + p; ++k) { + if (k < rangeStart || k == i) continue; + if (k >= n) break; + if (k < i && !greater(x, v[k])) { + good = false; + break; + } + if (k > i && greater(v[k], x)) { + good = false; + break; + } + } + if (good) { + m_locations[nPeaks++] = i; + } + } + + int pp = rangeStart - 1; + for (int i = rangeStart, j = 0; i < n; ++i) { + int np = i; + if (j < nPeaks) { + np = m_locations[j]; + } else if (nPeaks > 0) { + np = m_locations[nPeaks-1]; + } + if (next) { + if (pp == i || j >= nPeaks) { + next[i] = i; + } else { + next[i] = np; + } + } + if (nearest) { + if (j == 0) { + nearest[i] = np; + } else { + if (np - i <= i - pp) { + nearest[i] = np; + } else { + nearest[i] = pp; + } + } + } + while (j < nPeaks && m_locations[j] <= i) { + pp = np; + ++j; + } + } + } + +protected: + int m_n; + std::vector m_locations; +}; + + +} + +#endif diff --git a/src/finer/PhaseAdvance.h b/src/finer/PhaseAdvance.h new file mode 100644 index 0000000..b2dd33c --- /dev/null +++ b/src/finer/PhaseAdvance.h @@ -0,0 +1,260 @@ +/* -*- 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-2022 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 RUBBERBAND_PHASE_ADVANCE_H +#define RUBBERBAND_PHASE_ADVANCE_H + +#include "Guide.h" + +#include "../common/Log.h" +#include "../common/mathmisc.h" + +#include +#include + +namespace RubberBand +{ + +class GuidedPhaseAdvance +{ +public: + struct Parameters { + int fftSize; + double sampleRate; + int channels; + Parameters(int _fftSize, double _sampleRate, int _channels) : + fftSize(_fftSize), sampleRate(_sampleRate), channels(_channels) { } + }; + + GuidedPhaseAdvance(Parameters parameters, Log log) : + m_parameters(parameters), + m_log(log), + m_binCount(parameters.fftSize / 2 + 1), + m_peakPicker(m_binCount), + m_reported(false) { + int ch = m_parameters.channels; + m_currentPeaks = allocate_and_zero_channels(ch, m_binCount); + m_prevPeaks = allocate_and_zero_channels(ch, m_binCount); + m_greatestChannel = allocate_and_zero(m_binCount); + m_prevInPhase = allocate_and_zero_channels(ch, m_binCount); + m_prevOutPhase = allocate_and_zero_channels(ch, m_binCount); + m_unlocked = allocate_and_zero_channels(ch, m_binCount); + + for (int c = 0; c < ch; ++c) { + for (int i = 0; i < m_binCount; ++i) { + m_prevPeaks[c][i] = i; + } + } + } + + ~GuidedPhaseAdvance() { + int ch = m_parameters.channels; + deallocate_channels(m_currentPeaks, ch); + deallocate_channels(m_prevPeaks, ch); + deallocate(m_greatestChannel); + deallocate_channels(m_prevInPhase, ch); + deallocate_channels(m_prevOutPhase, ch); + deallocate_channels(m_unlocked, ch); + } + + void reset() { + int ch = m_parameters.channels; + v_zero_channels(m_prevPeaks, ch, m_binCount); + v_zero_channels(m_prevInPhase, ch, m_binCount); + v_zero_channels(m_prevOutPhase, ch, m_binCount); + } + + void advance(double *const *outPhase, + const double *const *mag, + const double *const *phase, + const double *const *prevMag, + const Guide::Configuration &configuration, + const Guide::Guidance *const *guidance, + int inhop, + int outhop) { + + int myFftBand = 0; + int i = 0; + for (const auto &fband : guidance[0]->fftBands) { + if (fband.fftSize == m_parameters.fftSize) { + myFftBand = i; + break; + } + ++i; + } + + int bs = m_parameters.fftSize / 2 + 1; + int channels = m_parameters.channels; + double ratio = double(outhop) / double(inhop); + + int lowest = configuration.fftBandLimits[myFftBand].b0min; + int highest = configuration.fftBandLimits[myFftBand].b1max; + + if (m_log.getDebugLevel() > 0 && !m_reported) { + m_log.log(1, "PhaseAdvance: for fftSize and bins", + m_parameters.fftSize, bs); + m_log.log(1, "PhaseAdvance: channels", channels); + m_log.log(1, "PhaseAdvance: widest bin range for this size", + lowest, highest); + m_log.log(1, "PhaseAdvance: widest freq range for this size", + configuration.fftBandLimits[myFftBand].f0min, + configuration.fftBandLimits[myFftBand].f1max); + m_log.log(1, "PhaseAdvance: initial inhop and outhop", + inhop, outhop); + m_log.log(1, "PhaseAdvance: initial ratio", ratio); + m_reported = true; + } + + for (int c = 0; c < channels; ++c) { + for (int i = lowest; i <= highest; ++i) { + m_currentPeaks[c][i] = i; + } + for (const auto &band : guidance[c]->phaseLockBands) { + int startBin = binForFrequency + (band.f0, m_parameters.fftSize, m_parameters.sampleRate); + int endBin = binForFrequency + (band.f1, m_parameters.fftSize, m_parameters.sampleRate); + if (startBin > highest || endBin < lowest) continue; + int count = endBin - startBin + 1; + m_peakPicker.findNearestAndNextPeaks(mag[c], + startBin, count, + band.p, m_currentPeaks[c], + nullptr); + } + m_peakPicker.findNearestAndNextPeaks(prevMag[c], + lowest, highest - lowest + 1, + 1, m_prevPeaks[c], + nullptr); + } + + if (channels > 1) { + for (int i = lowest; i <= highest; ++i) { + int gc = 0; + float gmag = mag[0][i]; + for (int c = 1; c < channels; ++c) { + if (mag[c][i] > gmag) { + gmag = mag[c][i]; + gc = c; + } + } + m_greatestChannel[i] = gc; + } + } else { + v_zero(m_greatestChannel, bs); + } + + double omegaFactor = 2.0 * M_PI * double(inhop) / + double(m_parameters.fftSize); + for (int c = 0; c < channels; ++c) { + for (int i = lowest; i <= highest; ++i) { + double omega = omegaFactor * double(i); + double expected = m_prevInPhase[c][i] + omega; + double error = princarg(phase[c][i] - expected); + double advance = ratio * (omega + error); + m_unlocked[c][i] = m_prevOutPhase[c][i] + advance; + } + } + + for (int c = 0; c < channels; ++c) { + const Guide::Guidance *g = guidance[c]; + int phaseLockBand = 0; + for (int i = lowest; i <= highest; ++i) { + double f = frequencyForBin + (i, m_parameters.fftSize, m_parameters.sampleRate); + while (f > g->phaseLockBands[phaseLockBand].f1) { + ++phaseLockBand; + } + double ph = 0.0; + if (inRange(f, g->phaseReset) || inRange(f, g->kick)) { + ph = phase[c][i]; + } else if (inhop == outhop) { + ph = m_unlocked[c][i]; + } else if (inRange (f, g->highUnlocked)) { + ph = m_unlocked[c][i]; + } else { + int peak = m_currentPeaks[c][i]; + int prevPeak = m_prevPeaks[c][peak]; + int peakCh = c; + if (inRange(f, g->channelLock)) { + int other = m_greatestChannel[i]; + if (other != c && + inRange(f, guidance[other]->channelLock)) { + int otherPeak = m_currentPeaks[other][i]; + int otherPrevPeak = m_prevPeaks[other][otherPeak]; + if (otherPrevPeak == prevPeak) { + peakCh = other; + } + } + } + double peakAdvance = + m_unlocked[peakCh][peak] - m_prevOutPhase[peakCh][peak]; + double peakNew = + m_prevOutPhase[peakCh][prevPeak] + peakAdvance; + double diff = + double(phase[c][i]) - double(phase[peakCh][peak]); + double beta = + double(g->phaseLockBands[phaseLockBand].beta); + ph = peakNew + beta * diff; + } + outPhase[c][i] = princarg(ph); + } + } + + for (int c = 0; c < channels; ++c) { + for (int i = lowest; i <= highest; ++i) { + m_prevInPhase[c][i] = phase[c][i]; + } + for (int i = lowest; i <= highest; ++i) { + m_prevOutPhase[c][i] = outPhase[c][i]; + } + } + } + + void setDebugLevel(int debugLevel) { + m_log.setDebugLevel(debugLevel); + } + +protected: + Parameters m_parameters; + Log m_log; + int m_binCount; + Peak m_peakPicker; + int **m_currentPeaks; + int **m_prevPeaks; + int *m_greatestChannel; + double **m_prevInPhase; + double **m_prevOutPhase; + double **m_unlocked; + bool m_reported; + + bool inRange(double f, const Guide::Range &r) { + return r.present && f >= r.f0 && f < r.f1; + } + + GuidedPhaseAdvance(const GuidedPhaseAdvance &) =delete; + GuidedPhaseAdvance &operator=(const GuidedPhaseAdvance &) =delete; +}; + +} + +#endif diff --git a/src/finer/R3Stretcher.cpp b/src/finer/R3Stretcher.cpp new file mode 100644 index 0000000..630b3ca --- /dev/null +++ b/src/finer/R3Stretcher.cpp @@ -0,0 +1,1184 @@ +/* -*- 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-2022 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 "R3Stretcher.h" + +#include "../common/VectorOpsComplex.h" + +#include + +namespace RubberBand { + +R3Stretcher::R3Stretcher(Parameters parameters, + double initialTimeRatio, + double initialPitchScale, + Log log) : + m_parameters(parameters), + m_log(log), + m_timeRatio(initialTimeRatio), + m_pitchScale(initialPitchScale), + m_formantScale(0.0), + m_guide(Guide::Parameters(m_parameters.sampleRate), m_log), + m_guideConfiguration(m_guide.getConfiguration()), + m_channelAssembly(m_parameters.channels), + m_inhop(1), + m_prevInhop(1), + m_prevOuthop(1), + m_unityCount(0), + m_startSkip(0), + m_studyInputDuration(0), + m_totalTargetDuration(0), + m_processInputDuration(0), + m_lastKeyFrameSurpassed(0), + m_totalOutputDuration(0), + m_mode(ProcessMode::JustCreated) +{ + m_log.log(1, "R3Stretcher::R3Stretcher: rate, options", + m_parameters.sampleRate, m_parameters.options); + m_log.log(1, "R3Stretcher::R3Stretcher: initial time ratio and pitch scale", + m_timeRatio, m_pitchScale); + + double maxClassifierFrequency = 16000.0; + if (maxClassifierFrequency > m_parameters.sampleRate/2) { + maxClassifierFrequency = m_parameters.sampleRate/2; + } + int classificationBins = + int(floor(m_guideConfiguration.classificationFftSize * + maxClassifierFrequency / m_parameters.sampleRate)); + + BinSegmenter::Parameters segmenterParameters + (m_guideConfiguration.classificationFftSize, + classificationBins, m_parameters.sampleRate, 18); + + BinClassifier::Parameters classifierParameters + (classificationBins, 9, 1, 10, 2.0, 2.0); + + int inRingBufferSize = m_guideConfiguration.longestFftSize * 2; + int outRingBufferSize = m_guideConfiguration.longestFftSize * 16; + + for (int c = 0; c < m_parameters.channels; ++c) { + m_channelData.push_back(std::make_shared + (segmenterParameters, + classifierParameters, + m_guideConfiguration.longestFftSize, + inRingBufferSize, + outRingBufferSize)); + for (auto band: m_guideConfiguration.fftBandLimits) { + int fftSize = band.fftSize; + m_channelData[c]->scales[fftSize] = + std::make_shared + (fftSize, m_guideConfiguration.longestFftSize); + } + } + + for (auto band: m_guideConfiguration.fftBandLimits) { + int fftSize = band.fftSize; + GuidedPhaseAdvance::Parameters guidedParameters + (fftSize, m_parameters.sampleRate, m_parameters.channels); + m_scaleData[fftSize] = std::make_shared + (guidedParameters, m_log); + } + + m_calculator = std::unique_ptr + (new StretchCalculator(int(round(m_parameters.sampleRate)), //!!! which is a double... + 1, false, // no fixed inputIncrement + m_log)); + + if (isRealTime()) { + createResampler(); + // In offline mode we don't create the resampler yet - we + // don't want to have one at all if the pitch ratio is 1.0, + // but that could change before the first process call, so we + // create the resampler if needed then + } + + calculateHop(); + + m_prevInhop = m_inhop; + m_prevOuthop = int(round(m_inhop * getEffectiveRatio())); + + if (!m_inhop.is_lock_free()) { + m_log.log(0, "WARNING: std::atomic is not lock-free"); + } + if (!m_timeRatio.is_lock_free()) { + m_log.log(0, "WARNING: std::atomic is not lock-free"); + } +} + +WindowType +R3Stretcher::ScaleData::analysisWindowShape(int fftSize) +{ + if (fftSize > 2048) return HannWindow; + else return NiemitaloForwardWindow; +} + +int +R3Stretcher::ScaleData::analysisWindowLength(int fftSize) +{ + return fftSize; +} + +WindowType +R3Stretcher::ScaleData::synthesisWindowShape(int fftSize) +{ + if (fftSize > 2048) return HannWindow; + else return NiemitaloReverseWindow; +} + +int +R3Stretcher::ScaleData::synthesisWindowLength(int fftSize) +{ + if (fftSize > 2048) return fftSize/2; + else return fftSize; +} + +void +R3Stretcher::setTimeRatio(double ratio) +{ + if (!isRealTime()) { + if (m_mode == ProcessMode::Studying || + m_mode == ProcessMode::Processing) { + m_log.log(0, "R3Stretcher::setTimeRatio: Cannot set time ratio while studying or processing in non-RT mode"); + return; + } + } + + if (ratio == m_timeRatio) return; + m_timeRatio = ratio; + calculateHop(); +} + +void +R3Stretcher::setPitchScale(double scale) +{ + if (!isRealTime()) { + if (m_mode == ProcessMode::Studying || + m_mode == ProcessMode::Processing) { + m_log.log(0, "R3Stretcher::setTimeRatio: Cannot set pitch scale while studying or processing in non-RT mode"); + return; + } + } + + if (scale == m_pitchScale) return; + m_pitchScale = scale; + calculateHop(); +} + +void +R3Stretcher::setFormantScale(double scale) +{ + if (!isRealTime()) { + if (m_mode == ProcessMode::Studying || + m_mode == ProcessMode::Processing) { + m_log.log(0, "R3Stretcher::setTimeRatio: Cannot set formant scale while studying or processing in non-RT mode"); + return; + } + } + + m_formantScale = scale; +} + +void +R3Stretcher::setFormantOption(RubberBandStretcher::Options options) +{ + int mask = (RubberBandStretcher::OptionFormantShifted | + RubberBandStretcher::OptionFormantPreserved); + m_parameters.options &= ~mask; + options &= mask; + m_parameters.options |= options; +} + +void +R3Stretcher::setKeyFrameMap(const std::map &mapping) +{ + if (isRealTime()) { + m_log.log(0, "R3Stretcher::setKeyFrameMap: Cannot specify key frame map in RT mode"); + return; + } + if (m_mode == ProcessMode::Processing || m_mode == ProcessMode::Finished) { + m_log.log(0, "R3Stretcher::setKeyFrameMap: Cannot specify key frame map after process() has begun"); + return; + } + + m_keyFrameMap = mapping; +} + +void +R3Stretcher::createResampler() +{ + Resampler::Parameters resamplerParameters; + resamplerParameters.quality = Resampler::FastestTolerable; + resamplerParameters.initialSampleRate = m_parameters.sampleRate; + resamplerParameters.maxBufferSize = m_guideConfiguration.longestFftSize; + + if (isRealTime()) { + resamplerParameters.dynamism = Resampler::RatioOftenChanging; + resamplerParameters.ratioChange = Resampler::SmoothRatioChange; + } else { + // ratio can't be changed in offline mode + resamplerParameters.dynamism = Resampler::RatioMostlyFixed; + resamplerParameters.ratioChange = Resampler::SuddenRatioChange; + } + + m_resampler = std::unique_ptr + (new Resampler(resamplerParameters, m_parameters.channels)); +} + +void +R3Stretcher::calculateHop() +{ + double ratio = getEffectiveRatio(); + + // In R2 we generally targeted a certain inhop, and calculated + // outhop from that. Here we are the other way around. We aim for + // outhop = 256 at ratios around 1, reducing down to 128 for + // ratios far below 1 and up to 512 for ratios far above. As soon + // as outhop exceeds 256 we have to drop the 1024-bin FFT, as the + // overlap will be inadequate for it (that's among the jobs of the + // Guide class) so we don't want to go above 256 until at least + // factor 1.5. Also we can't go above 512 without changing the + // window shape or dropping the 2048-bin FFT, and we can't do + // either of those dynamically. + + double proposedOuthop = 256.0; + if (ratio > 1.5) { + proposedOuthop = pow(2.0, 8.0 + 2.0 * log10(ratio - 0.5)); + } else if (ratio < 1.0) { + proposedOuthop = pow(2.0, 8.0 + 2.0 * log10(ratio)); + } + if (proposedOuthop > 512.0) proposedOuthop = 512.0; + if (proposedOuthop < 128.0) proposedOuthop = 128.0; + + m_log.log(1, "calculateHop: ratio and proposed outhop", ratio, proposedOuthop); + + double inhop = proposedOuthop / ratio; + if (inhop < 1.0) { + m_log.log(0, "WARNING: Extreme ratio yields ideal inhop < 1, results may be suspect", ratio, inhop); + inhop = 1.0; + } + if (inhop > 768.0) { + m_log.log(0, "WARNING: Extreme ratio yields ideal inhop > 768, results may be suspect", ratio, inhop); + inhop = 768.0; + } + + m_inhop = int(floor(inhop)); + + m_log.log(1, "calculateHop: inhop and mean outhop", m_inhop, m_inhop * ratio); +} + +void +R3Stretcher::updateRatioFromMap() +{ + if (m_keyFrameMap.empty()) return; + + if (m_processInputDuration == 0) { + m_timeRatio = double(m_keyFrameMap.begin()->second) / + double(m_keyFrameMap.begin()->first); + + m_log.log(1, "initial key-frame map entry ", + double(m_keyFrameMap.begin()->first), + double(m_keyFrameMap.begin()->second)); + m_log.log(1, "giving initial ratio ", m_timeRatio); + + calculateHop(); + m_lastKeyFrameSurpassed = 0; + return; + } + + auto i0 = m_keyFrameMap.upper_bound(m_lastKeyFrameSurpassed); + + if (i0 == m_keyFrameMap.end()) { + return; + } + + if (m_processInputDuration >= i0->first) { + + m_log.log(1, "input duration surpasses pending key frame", + double(m_processInputDuration), double(i0->first)); + + auto i1 = m_keyFrameMap.upper_bound(m_processInputDuration); + + size_t keyFrameAtInput, keyFrameAtOutput; + + if (i1 != m_keyFrameMap.end()) { + keyFrameAtInput = i1->first; + keyFrameAtOutput = i1->second; + } else { + keyFrameAtInput = m_studyInputDuration; + keyFrameAtOutput = m_totalTargetDuration; + } + + size_t toKeyFrameAtInput = keyFrameAtInput - i0->first; + size_t toKeyFrameAtOutput = keyFrameAtOutput - i0->second; + + double ratio = double(toKeyFrameAtOutput) / double(toKeyFrameAtInput); + + m_log.log(1, "next key frame input and output", + double(keyFrameAtInput), double(keyFrameAtOutput)); + m_log.log(1, "diff to next key frame input and output", + double(toKeyFrameAtInput), double(toKeyFrameAtOutput)); + m_log.log(1, "current input and output", + double(m_processInputDuration), double(m_totalOutputDuration)); + m_log.log(1, "new ratio", ratio); + + m_timeRatio = ratio; + calculateHop(); + + m_lastKeyFrameSurpassed = i0->first; + } +} + +double +R3Stretcher::getTimeRatio() const +{ + return m_timeRatio; +} + +double +R3Stretcher::getPitchScale() const +{ + return m_pitchScale; +} + +double +R3Stretcher::getFormantScale() const +{ + return m_formantScale; +} + +size_t +R3Stretcher::getLatency() const +{ + if (!isRealTime()) { + return 0; + } else { + return size_t(ceil(m_guideConfiguration.longestFftSize + * 0.5 * m_pitchScale)); + } +} + +size_t +R3Stretcher::getChannelCount() const +{ + return m_parameters.channels; +} + +void +R3Stretcher::reset() +{ + m_calculator->reset(); + if (m_resampler) { + m_resampler->reset(); + } + + for (auto &it : m_scaleData) { + it.second->guided.reset(); + } + + for (auto &cd : m_channelData) { + cd->reset(); + } + + m_prevInhop = m_inhop; + m_prevOuthop = int(round(m_inhop * getEffectiveRatio())); + + m_studyInputDuration = 0; + m_totalTargetDuration = 0; + m_processInputDuration = 0; + m_lastKeyFrameSurpassed = 0; + m_totalOutputDuration = 0; + m_keyFrameMap.clear(); + + m_mode = ProcessMode::JustCreated; +} + +void +R3Stretcher::study(const float *const *, size_t samples, bool) +{ + if (isRealTime()) { + m_log.log(0, "R3Stretcher::study: Not meaningful in realtime mode"); + return; + } + + if (m_mode == ProcessMode::Processing || m_mode == ProcessMode::Finished) { + m_log.log(0, "R3Stretcher::study: Cannot study after processing"); + return; + } + + if (m_mode == ProcessMode::JustCreated) { + m_studyInputDuration = 0; + } + + m_mode = ProcessMode::Studying; + m_studyInputDuration += samples; +} + +size_t +R3Stretcher::getSamplesRequired() const +{ + if (available() != 0) return 0; + int longest = m_guideConfiguration.longestFftSize; + int rs = m_channelData[0]->inbuf->getReadSpace(); + if (rs < longest) { + return longest - rs; + } else { + return 0; + } +} + +void +R3Stretcher::setMaxProcessSize(size_t n) +{ + size_t oldSize = m_channelData[0]->inbuf->getSize(); + size_t newSize = m_guideConfiguration.longestFftSize + n; + + if (newSize > oldSize) { + m_log.log(1, "setMaxProcessSize: resizing from and to", oldSize, newSize); + for (int c = 0; c < m_parameters.channels; ++c) { + m_channelData[c]->inbuf = std::unique_ptr> + (m_channelData[c]->inbuf->resized(newSize)); + } + } else { + m_log.log(1, "setMaxProcessSize: nothing to be done, newSize <= oldSize", newSize, oldSize); + } +} + +void +R3Stretcher::process(const float *const *input, size_t samples, bool final) +{ + if (m_mode == ProcessMode::Finished) { + m_log.log(0, "R3Stretcher::process: Cannot process again after final chunk"); + return; + } + + if (!isRealTime()) { + + if (m_mode == ProcessMode::Studying) { + m_totalTargetDuration = + size_t(round(m_studyInputDuration * m_timeRatio)); + m_log.log(1, "study duration and target duration", + m_studyInputDuration, m_totalTargetDuration); + } + + // Update this on every process round, checking whether we've + // surpassed the next key frame yet. This must follow the + // overall target calculation above, which uses the "global" + // time ratio, but precede any other use of the time ratio. + + if (!m_keyFrameMap.empty()) { + updateRatioFromMap(); + } + + if (m_mode == ProcessMode::JustCreated || + m_mode == ProcessMode::Studying) { + + if (m_pitchScale != 1.0 && !m_resampler) { + createResampler(); + } + + // Pad to half the longest frame. As with R2, in real-time + // mode we don't do this -- it's better to start with a + // swoosh than introduce more latency, and we don't want + // gaps when the ratio changes. + int pad = m_guideConfiguration.longestFftSize / 2; + m_log.log(1, "offline mode: prefilling with", pad); + for (int c = 0; c < m_parameters.channels; ++c) { + m_channelData[c]->inbuf->zero(pad); + } + + // NB by the time we skip this later we may have resampled + // as well as stretched + m_startSkip = int(round(pad / m_pitchScale)); + if (m_startSkip < 0) { + m_log.log(0, "WARNING: calculated start skip < 0", m_startSkip); + m_startSkip = 0; + } else { + m_log.log(1, "start skip is", m_startSkip); + } + } + } + + if (final) { + // We don't distinguish between Finished and "draining, but + // haven't yet delivered all the samples" because the + // distinction is meaningless internally - it only affects + // whether available() finds any samples in the buffer + m_mode = ProcessMode::Finished; + } else { + m_mode = ProcessMode::Processing; + } + + size_t ws = m_channelData[0]->inbuf->getWriteSpace(); + if (samples > ws) { + m_log.log(0, "R3Stretcher::process: WARNING: Forced to increase input buffer size. Either setMaxProcessSize was not properly called or process is being called repeatedly without retrieve. Write space and samples", ws, samples); + size_t newSize = m_channelData[0]->inbuf->getSize() - ws + samples; + for (int c = 0; c < m_parameters.channels; ++c) { + auto newBuf = m_channelData[c]->inbuf->resized(newSize); + m_channelData[c]->inbuf = std::unique_ptr>(newBuf); + } + } + + for (int c = 0; c < m_parameters.channels; ++c) { + m_channelData[c]->inbuf->write(input[c], samples); + } + + m_processInputDuration += samples; + + consume(); +} + +int +R3Stretcher::available() const +{ + int av = int(m_channelData[0]->outbuf->getReadSpace()); + if (av == 0 && m_mode == ProcessMode::Finished) { + return -1; + } else { + return av; + } +} + +size_t +R3Stretcher::retrieve(float *const *output, size_t samples) const +{ + int got = samples; + + for (int c = 0; c < m_parameters.channels; ++c) { + int gotHere = m_channelData[c]->outbuf->read(output[c], got); + if (gotHere < got) { + if (c > 0) { + m_log.log(0, "R3Stretcher::retrieve: WARNING: channel imbalance detected"); + } + got = std::min(got, std::max(gotHere, 0)); + } + } + + return got; +} + +void +R3Stretcher::consume() +{ + int longest = m_guideConfiguration.longestFftSize; + int channels = m_parameters.channels; + int inhop = m_inhop; + + double effectivePitchRatio = 1.0 / m_pitchScale; + if (m_resampler) { + effectivePitchRatio = + m_resampler->getEffectiveRatio(effectivePitchRatio); + } + + int outhop = m_calculator->calculateSingle(m_timeRatio, + effectivePitchRatio, + 1.f, + inhop, + longest, + longest, + true); + + // Now inhop is the distance by which the input stream will be + // advanced after our current frame has been read, and outhop is + // the distance by which the output will be advanced after it has + // been emitted; m_prevInhop and m_prevOuthop are the + // corresponding values the last time a frame was processed (*not* + // just the last time this function was called, since we can + // return without doing anything if the output buffer is full). + // + // Our phase adjustments need to be based on the distances we have + // advanced the input and output since the previous frame, not the + // distances we are about to advance them, so they use the m_prev + // values. + + if (inhop != m_prevInhop) { + m_log.log(2, "change in inhop", double(m_prevInhop), double(inhop)); + } + if (outhop != m_prevOuthop) { + m_log.log(2, "change in outhop", double(m_prevOuthop), double(outhop)); + } + + while (m_channelData.at(0)->outbuf->getWriteSpace() >= outhop) { + + // NB our ChannelData, ScaleData, and ChannelScaleData maps + // contain shared_ptrs; whenever we retain one of them in a + // variable, we do so by reference to avoid copying the + // shared_ptr (as that is not realtime safe). Same goes for + // the map iterators + + int readSpace = m_channelData.at(0)->inbuf->getReadSpace(); + if (readSpace < longest) { + if (m_mode == ProcessMode::Finished) { + if (readSpace == 0) { + break; + } + } else { + break; + } + } + + // Analysis + + for (int c = 0; c < channels; ++c) { + analyseChannel(c, inhop, m_prevInhop, m_prevOuthop); + } + + // Phase update. This is synchronised across all channels + + for (auto &it : m_channelData[0]->scales) { + int fftSize = it.first; + for (int c = 0; c < channels; ++c) { + auto &cd = m_channelData.at(c); + auto &scale = cd->scales.at(fftSize); + m_channelAssembly.mag[c] = scale->mag.data(); + m_channelAssembly.phase[c] = scale->phase.data(); + m_channelAssembly.prevMag[c] = scale->prevMag.data(); + m_channelAssembly.guidance[c] = &cd->guidance; + m_channelAssembly.outPhase[c] = scale->advancedPhase.data(); + } + m_scaleData.at(fftSize)->guided.advance + (m_channelAssembly.outPhase.data(), + m_channelAssembly.mag.data(), + m_channelAssembly.phase.data(), + m_channelAssembly.prevMag.data(), + m_guideConfiguration, + m_channelAssembly.guidance.data(), + m_prevInhop, + m_prevOuthop); + } + + for (int c = 0; c < channels; ++c) { + adjustPreKick(c); + } + + // Resynthesis + + for (int c = 0; c < channels; ++c) { + synthesiseChannel(c, outhop); + } + + // We now have outhop samples at the start of the mixdown + // buffer, but they aren't necessarily all valid, because we + // might have had fewer than outhop at the start of inbuf + // (when finished and draining) + + int validCount = outhop; + if (readSpace < outhop) { + validCount = readSpace; + } + + // Resample + + int resampledCount = 0; + if (m_resampler) { + for (int c = 0; c < channels; ++c) { + auto &cd = m_channelData.at(c); + m_channelAssembly.mixdown[c] = cd->mixdown.data(); + m_channelAssembly.resampled[c] = cd->resampled.data(); + } + resampledCount = m_resampler->resample + (m_channelAssembly.resampled.data(), + m_channelData[0]->resampled.size(), + m_channelAssembly.mixdown.data(), + validCount, + 1.0 / m_pitchScale, + m_mode == ProcessMode::Finished && readSpace < inhop); + } + + // Emit + + int writeCount = validCount; + if (m_resampler) { + writeCount = resampledCount; + } + + if (!isRealTime()) { + if (m_totalTargetDuration > 0 && + m_totalOutputDuration + writeCount > m_totalTargetDuration) { + m_log.log(1, "writeCount would take output beyond target", + m_totalOutputDuration, m_totalTargetDuration); + auto reduced = m_totalTargetDuration - m_totalOutputDuration; + m_log.log(1, "reducing writeCount from and to", writeCount, reduced); + writeCount = reduced; + } + } + + for (int c = 0; c < channels; ++c) { + auto &cd = m_channelData.at(c); + if (m_resampler) { + cd->outbuf->write(cd->resampled.data(), writeCount); + } else { + cd->outbuf->write(cd->mixdown.data(), writeCount); + } + + if (readSpace < inhop) { + // This should happen only when draining (Finished) + if (m_mode != ProcessMode::Finished) { + m_log.log(0, "WARNING: readSpace < inhop when processing is not yet finished", readSpace, inhop); + } + cd->inbuf->skip(readSpace); + } else { + cd->inbuf->skip(inhop); + } + } + + m_totalOutputDuration += writeCount; + + if (m_startSkip > 0) { + int rs = m_channelData.at(0)->outbuf->getReadSpace(); + int toSkip = std::min(m_startSkip, rs); + for (int c = 0; c < channels; ++c) { + m_channelData.at(c)->outbuf->skip(toSkip); + } + m_startSkip -= toSkip; + m_totalOutputDuration = rs - toSkip; + } + + m_prevInhop = inhop; + m_prevOuthop = outhop; + } +} + +void +R3Stretcher::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) +{ + int longest = m_guideConfiguration.longestFftSize; + int classify = m_guideConfiguration.classificationFftSize; + + auto &cd = m_channelData.at(c); + double *buf = cd->scales.at(longest)->timeDomain.data(); + + int readSpace = cd->inbuf->getReadSpace(); + if (readSpace < longest) { + cd->inbuf->peek(buf, readSpace); + v_zero(buf + readSpace, longest - readSpace); + } else { + cd->inbuf->peek(buf, longest); + } + + // We have a single unwindowed frame at the longest FFT size + // ("scale"). Populate the shorter FFT sizes from the centre of + // it, windowing as we copy. The classification scale is handled + // separately because it has readahead, so skip it here as well as + // the longest. (In practice this means we are probably only + // populating one scale) + + for (auto &it: cd->scales) { + int fftSize = it.first; + if (fftSize == classify || fftSize == longest) continue; + int offset = (longest - fftSize) / 2; + m_scaleData.at(fftSize)->analysisWindow.cut + (buf + offset, it.second->timeDomain.data()); + } + + // The classification scale has a one-hop readahead, so populate + // the readahead from further down the long unwindowed frame. + + auto &classifyScale = cd->scales.at(classify); + ClassificationReadaheadData &readahead = cd->readahead; + + m_scaleData.at(classify)->analysisWindow.cut + (buf + (longest - classify) / 2 + inhop, + readahead.timeDomain.data()); + + // If inhop has changed since the previous frame, we'll have to + // populate the classification scale (but for analysis/resynthesis + // rather than classification) anew rather than reuse the previous + // readahead. Pity... + + bool haveValidReadahead = cd->haveReadahead; + if (inhop != prevInhop) haveValidReadahead = false; + + if (!haveValidReadahead) { + m_scaleData.at(classify)->analysisWindow.cut + (buf + (longest - classify) / 2, + classifyScale->timeDomain.data()); + } + + // Finally window the longest scale + m_scaleData.at(longest)->analysisWindow.cut(buf); + + // FFT shift, forward FFT, and carry out cartesian-polar + // conversion for each FFT size. + + // For the classification scale we need magnitudes for the full + // range (polar only in a subset) and we operate in the readahead, + // pulling current values from the existing readahead (except + // where the inhop has changed as above, in which case we need to + // do both readahead and current) + + if (haveValidReadahead) { + v_copy(classifyScale->mag.data(), + readahead.mag.data(), + classifyScale->bufSize); + v_copy(classifyScale->phase.data(), + readahead.phase.data(), + classifyScale->bufSize); + } + + v_fftshift(readahead.timeDomain.data(), classify); + m_scaleData.at(classify)->fft.forward(readahead.timeDomain.data(), + classifyScale->real.data(), + classifyScale->imag.data()); + + for (const auto &b : m_guideConfiguration.fftBandLimits) { + if (b.fftSize == classify) { + + ToPolarSpec spec; + spec.magFromBin = 0; + spec.magBinCount = classify/2 + 1; + spec.polarFromBin = b.b0min; + spec.polarBinCount = b.b1max - b.b0min + 1; + convertToPolar(readahead.mag.data(), + readahead.phase.data(), + classifyScale->real.data(), + classifyScale->imag.data(), + spec); + + v_scale(classifyScale->mag.data(), + 1.0 / double(classify), + classifyScale->mag.size()); + break; + } + } + + cd->haveReadahead = true; + + // For the others (and the classify as well, if the inhop has + // changed or we haven't filled the readahead yet) we operate + // directly in the scale data and restrict the range for + // cartesian-polar conversion + + for (auto &it: cd->scales) { + int fftSize = it.first; + if (fftSize == classify && haveValidReadahead) { + continue; + } + + auto &scale = it.second; + + v_fftshift(scale->timeDomain.data(), fftSize); + + m_scaleData.at(fftSize)->fft.forward(scale->timeDomain.data(), + scale->real.data(), + scale->imag.data()); + + for (const auto &b : m_guideConfiguration.fftBandLimits) { + if (b.fftSize == fftSize) { + + ToPolarSpec spec; + + // For the classify scale we always want the full + // range, as all the magnitudes (though not + // necessarily all phases) are potentially relevant to + // classification and formant analysis. But this case + // here only happens if we don't haveValidReadahead - + // the normal case is above and just copies from the + // previous readahead. + if (fftSize == classify) { + spec.magFromBin = 0; + spec.magBinCount = classify/2 + 1; + spec.polarFromBin = b.b0min; + spec.polarBinCount = b.b1max - b.b0min + 1; + } else { + spec.magFromBin = b.b0min; + spec.magBinCount = b.b1max - b.b0min + 1; + spec.polarFromBin = spec.magFromBin; + spec.polarBinCount = spec.magBinCount; + } + + convertToPolar(scale->mag.data(), + scale->phase.data(), + scale->real.data(), + scale->imag.data(), + spec); + + v_scale(scale->mag.data() + spec.magFromBin, + 1.0 / double(fftSize), + spec.magBinCount); + + break; + } + } + } + + if (m_parameters.options & RubberBandStretcher::OptionFormantPreserved) { + analyseFormant(c); + adjustFormant(c); + } + + // Use the classification scale to get a bin segmentation and + // calculate the adaptive frequency guide for this channel + + v_copy(cd->classification.data(), cd->nextClassification.data(), + cd->classification.size()); + cd->classifier->classify(readahead.mag.data(), + cd->nextClassification.data()); + + cd->prevSegmentation = cd->segmentation; + cd->segmentation = cd->nextSegmentation; + cd->nextSegmentation = cd->segmenter->segment(cd->nextClassification.data()); +/* + if (c == 0) { + double pb = cd->nextSegmentation.percussiveBelow; + double pa = cd->nextSegmentation.percussiveAbove; + double ra = cd->nextSegmentation.residualAbove; + int pbb = binForFrequency(pb, classify, m_parameters.sampleRate); + int pab = binForFrequency(pa, classify, m_parameters.sampleRate); + int rab = binForFrequency(ra, classify, m_parameters.sampleRate); + std::cout << "pb = " << pb << ", pbb = " << pbb << std::endl; + std::cout << "pa = " << pa << ", pab = " << pab << std::endl; + std::cout << "ra = " << ra << ", rab = " << rab << std::endl; + std::cout << "s:"; + for (int i = 0; i <= classify/2; ++i) { + if (i > 0) std::cout << ","; + if (i < pbb || (i >= pab && i <= rab)) { + std::cout << "1"; + } else { + std::cout << "0"; + } + } + std::cout << std::endl; + } +*/ + + double ratio = getEffectiveRatio(); + + if (fabs(ratio - 1.0) < 1.0e-7) { + ++m_unityCount; + } else { + m_unityCount = 0; + } + + m_guide.updateGuidance(ratio, + prevOuthop, + classifyScale->mag.data(), + classifyScale->prevMag.data(), + cd->readahead.mag.data(), + cd->segmentation, + cd->prevSegmentation, + cd->nextSegmentation, + v_mean(classifyScale->mag.data() + 1, classify/2), + m_unityCount, + isRealTime(), + cd->guidance); +/* + if (c == 0) { + if (cd->guidance.kick.present) { + std::cout << "k:2" << std::endl; + } else if (cd->guidance.preKick.present) { + std::cout << "k:1" << std::endl; + } else { + std::cout << "k:0" << std::endl; + } + } +*/ +} + +void +R3Stretcher::analyseFormant(int c) +{ + auto &cd = m_channelData.at(c); + auto &f = *cd->formant; + + int fftSize = f.fftSize; + int binCount = fftSize/2 + 1; + + auto &scale = cd->scales.at(fftSize); + auto &scaleData = m_scaleData.at(fftSize); + + scaleData->fft.inverseCepstral(scale->mag.data(), f.cepstra.data()); + + int cutoff = int(floor(m_parameters.sampleRate / 650.0)); + if (cutoff < 1) cutoff = 1; + + f.cepstra[0] /= 2.0; + f.cepstra[cutoff-1] /= 2.0; + for (int i = cutoff; i < fftSize; ++i) { + f.cepstra[i] = 0.0; + } + v_scale(f.cepstra.data(), 1.0 / double(fftSize), cutoff); + + scaleData->fft.forward(f.cepstra.data(), f.envelope.data(), f.spare.data()); + + v_exp(f.envelope.data(), binCount); + v_square(f.envelope.data(), binCount); + + for (int i = 0; i < binCount; ++i) { + if (f.envelope[i] > 1.0e10) f.envelope[i] = 1.0e10; + } +} + +void +R3Stretcher::adjustFormant(int c) +{ + auto &cd = m_channelData.at(c); + + for (auto &it : cd->scales) { + + int fftSize = it.first; + auto &scale = it.second; + + int highBin = int(floor(fftSize * 10000.0 / m_parameters.sampleRate)); + double targetFactor = double(cd->formant->fftSize) / double(fftSize); + double formantScale = m_formantScale; + if (formantScale == 0.0) formantScale = 1.0 / m_pitchScale; + double sourceFactor = targetFactor / formantScale; + double maxRatio = 60.0; + double minRatio = 1.0 / maxRatio; + + for (const auto &b : m_guideConfiguration.fftBandLimits) { + if (b.fftSize != fftSize) continue; + for (int i = b.b0min; i < b.b1max && i < highBin; ++i) { + double source = cd->formant->envelopeAt(i * sourceFactor); + double target = cd->formant->envelopeAt(i * targetFactor); + if (target > 0.0) { + double ratio = source / target; + if (ratio < minRatio) ratio = minRatio; + if (ratio > maxRatio) ratio = maxRatio; + scale->mag[i] *= ratio; + } + } + } + } +} + +void +R3Stretcher::adjustPreKick(int c) +{ + auto &cd = m_channelData.at(c); + auto fftSize = cd->guidance.fftBands[0].fftSize; + if (cd->guidance.preKick.present) { + auto &scale = cd->scales.at(fftSize); + int from = binForFrequency(cd->guidance.preKick.f0, + fftSize, m_parameters.sampleRate); + int to = binForFrequency(cd->guidance.preKick.f1, + fftSize, m_parameters.sampleRate); + for (int i = from; i <= to; ++i) { + double diff = scale->mag[i] - scale->prevMag[i]; + if (diff > 0.0) { + scale->pendingKick[i] = diff; + scale->mag[i] -= diff; + } + } + } else if (cd->guidance.kick.present) { + auto &scale = cd->scales.at(fftSize); + int from = binForFrequency(cd->guidance.preKick.f0, + fftSize, m_parameters.sampleRate); + int to = binForFrequency(cd->guidance.preKick.f1, + fftSize, m_parameters.sampleRate); + for (int i = from; i <= to; ++i) { + scale->mag[i] += scale->pendingKick[i]; + scale->pendingKick[i] = 0.0; + } + } +} + +void +R3Stretcher::synthesiseChannel(int c, int outhop) +{ + int longest = m_guideConfiguration.longestFftSize; + + auto &cd = m_channelData.at(c); + + for (const auto &band : cd->guidance.fftBands) { + int fftSize = band.fftSize; + auto &scale = cd->scales.at(fftSize); + auto &scaleData = m_scaleData.at(fftSize); + + // copy to prevMag before filtering + v_copy(scale->prevMag.data(), + scale->mag.data(), + scale->bufSize); + + double winscale = double(outhop) / scaleData->windowScaleFactor; + + // The frequency filter is applied naively in the frequency + // domain. Aliasing is reduced by the shorter resynthesis + // window. We resynthesise each scale individually, then sum - + // it's easier to manage scaling for in situations with a + // varying resynthesis hop + + int lowBin = binForFrequency(band.f0, fftSize, m_parameters.sampleRate); + int highBin = binForFrequency(band.f1, fftSize, m_parameters.sampleRate); + if (highBin % 2 == 0 && highBin > 0) --highBin; + + if (lowBin > 0) { + v_zero(scale->real.data(), lowBin); + v_zero(scale->imag.data(), lowBin); + } + + v_scale(scale->mag.data() + lowBin, winscale, highBin - lowBin); + + v_polar_to_cartesian(scale->real.data() + lowBin, + scale->imag.data() + lowBin, + scale->mag.data() + lowBin, + scale->advancedPhase.data() + lowBin, + highBin - lowBin); + + if (highBin < scale->bufSize) { + v_zero(scale->real.data() + highBin, scale->bufSize - highBin); + v_zero(scale->imag.data() + highBin, scale->bufSize - highBin); + } + + scaleData->fft.inverse(scale->real.data(), + scale->imag.data(), + scale->timeDomain.data()); + + v_fftshift(scale->timeDomain.data(), fftSize); + + // Synthesis window may be shorter than analysis window, so + // copy and cut only from the middle of the time-domain frame; + // and the accumulator length always matches the longest FFT + // size, so as to make mixing straightforward, so there is an + // additional offset needed for the target + + int synthesisWindowSize = scaleData->synthesisWindow.getSize(); + int fromOffset = (fftSize - synthesisWindowSize) / 2; + int toOffset = (longest - synthesisWindowSize) / 2; + + scaleData->synthesisWindow.cutAndAdd + (scale->timeDomain.data() + fromOffset, + scale->accumulator.data() + toOffset); + } + + // Mix this channel and move the accumulator along + + float *mixptr = cd->mixdown.data(); + v_zero(mixptr, outhop); + + for (auto &it : cd->scales) { + auto &scale = it.second; + + double *accptr = scale->accumulator.data(); + for (int i = 0; i < outhop; ++i) { + mixptr[i] += float(accptr[i]); + } + + int n = scale->accumulator.size() - outhop; + v_move(accptr, accptr + outhop, n); + v_zero(accptr + n, outhop); + } +} + +} + diff --git a/src/finer/R3Stretcher.h b/src/finer/R3Stretcher.h new file mode 100644 index 0000000..3625518 --- /dev/null +++ b/src/finer/R3Stretcher.h @@ -0,0 +1,366 @@ +/* -*- 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-2022 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 RUBBERBAND_R3_STRETCHERIMPL_H +#define RUBBERBAND_R3_STRETCHERIMPL_H + +#include "BinSegmenter.h" +#include "Guide.h" +#include "Peak.h" +#include "PhaseAdvance.h" + +#include "../common/StretchCalculator.h" +#include "../common/Resampler.h" +#include "../common/FFT.h" +#include "../common/FixedVector.h" +#include "../common/Allocators.h" +#include "../common/Window.h" +#include "../common/VectorOpsComplex.h" +#include "../common/Log.h" + +#include "../../rubberband/RubberBandStretcher.h" + +#include +#include + +namespace RubberBand +{ + +class R3Stretcher +{ +public: + struct Parameters { + double sampleRate; + int channels; + RubberBandStretcher::Options options; + Parameters(double _sampleRate, int _channels, + RubberBandStretcher::Options _options) : + sampleRate(_sampleRate), channels(_channels), options(_options) { } + }; + + R3Stretcher(Parameters parameters, + double initialTimeRatio, + double initialPitchScale, + Log log); + ~R3Stretcher() { } + + void reset(); + + void setTimeRatio(double ratio); + void setPitchScale(double scale); + void setFormantScale(double scale); + + double getTimeRatio() const; + double getPitchScale() const; + double getFormantScale() const; + + void setKeyFrameMap(const std::map &); + + void setFormantOption(RubberBandStretcher::Options); + + void study(const float *const *input, size_t samples, bool final); + size_t getSamplesRequired() const; + void process(const float *const *input, size_t samples, bool final); + int available() const; + size_t retrieve(float *const *output, size_t samples) const; + + size_t getLatency() const; + size_t getChannelCount() const; + + void setMaxProcessSize(size_t samples); + + void setDebugLevel(int level) { + m_log.setDebugLevel(level); + for (auto &sd : m_scaleData) { + sd.second->guided.setDebugLevel(level); + } + m_guide.setDebugLevel(level); + m_calculator->setDebugLevel(level); + } + +protected: + struct ClassificationReadaheadData { + FixedVector timeDomain; + FixedVector mag; + FixedVector phase; + ClassificationReadaheadData(int _fftSize) : + timeDomain(_fftSize, 0.f), + mag(_fftSize/2 + 1, 0.f), + phase(_fftSize/2 + 1, 0.f) + { } + + private: + ClassificationReadaheadData(const ClassificationReadaheadData &) =delete; + ClassificationReadaheadData &operator=(const ClassificationReadaheadData &) =delete; + }; + + struct ChannelScaleData { + int fftSize; + int bufSize; // size of every freq-domain array here: fftSize/2 + 1 + FixedVector timeDomain; + FixedVector real; + FixedVector imag; + FixedVector mag; + FixedVector phase; + FixedVector advancedPhase; + FixedVector prevMag; + FixedVector pendingKick; + FixedVector accumulator; + + ChannelScaleData(int _fftSize, int _longestFftSize) : + fftSize(_fftSize), + bufSize(fftSize/2 + 1), + timeDomain(fftSize, 0.f), + real(bufSize, 0.f), + imag(bufSize, 0.f), + mag(bufSize, 0.f), + phase(bufSize, 0.f), + advancedPhase(bufSize, 0.f), + prevMag(bufSize, 0.f), + pendingKick(bufSize, 0.f), + accumulator(_longestFftSize, 0.f) + { } + + void reset() { + v_zero(prevMag.data(), prevMag.size()); + v_zero(accumulator.data(), accumulator.size()); + } + + private: + ChannelScaleData(const ChannelScaleData &) =delete; + ChannelScaleData &operator=(const ChannelScaleData &) =delete; + }; + + struct FormantData { + int fftSize; + FixedVector cepstra; + FixedVector envelope; + FixedVector spare; + + FormantData(int _fftSize) : + fftSize(_fftSize), + cepstra(_fftSize, 0.0), + envelope(_fftSize/2 + 1, 0.0), + spare(_fftSize/2 + 1, 0.0) { } + + double envelopeAt(double bin) const { + int b0 = int(floor(bin)), b1 = int(ceil(bin)); + if (b0 < 0 || b0 > fftSize/2) { + return 0.0; + } else if (b1 == b0 || b1 > fftSize/2) { + return envelope.at(b0); + } else { + double diff = bin - double(b0); + return envelope.at(b0) * (1.0 - diff) + envelope.at(b1) * diff; + } + } + }; + + struct ChannelData { + std::map> scales; + ClassificationReadaheadData readahead; + bool haveReadahead; + std::unique_ptr classifier; + FixedVector classification; + FixedVector nextClassification; + std::unique_ptr segmenter; + BinSegmenter::Segmentation segmentation; + BinSegmenter::Segmentation prevSegmentation; + BinSegmenter::Segmentation nextSegmentation; + Guide::Guidance guidance; + FixedVector mixdown; + FixedVector resampled; + std::unique_ptr> inbuf; + std::unique_ptr> outbuf; + std::unique_ptr formant; + ChannelData(BinSegmenter::Parameters segmenterParameters, + BinClassifier::Parameters classifierParameters, + int longestFftSize, + int inRingBufferSize, + int outRingBufferSize) : + scales(), + readahead(segmenterParameters.fftSize), + haveReadahead(false), + classifier(new BinClassifier(classifierParameters)), + classification(classifierParameters.binCount, + BinClassifier::Classification::Residual), + nextClassification(classifierParameters.binCount, + BinClassifier::Classification::Residual), + segmenter(new BinSegmenter(segmenterParameters)), + segmentation(), prevSegmentation(), nextSegmentation(), + mixdown(longestFftSize, 0.f), // though it could be shorter + resampled(outRingBufferSize, 0.f), + inbuf(new RingBuffer(inRingBufferSize)), + outbuf(new RingBuffer(outRingBufferSize)), + formant(new FormantData(segmenterParameters.fftSize)) { } + void reset() { + haveReadahead = false; + classifier->reset(); + segmentation = BinSegmenter::Segmentation(); + prevSegmentation = BinSegmenter::Segmentation(); + nextSegmentation = BinSegmenter::Segmentation(); + inbuf->reset(); + outbuf->reset(); + for (auto &s : scales) { + s.second->reset(); + } + } + }; + + struct ChannelAssembly { + // Vectors of bare pointers, used to package container data + // from different channels into arguments for PhaseAdvance + FixedVector mag; + FixedVector phase; + FixedVector prevMag; + FixedVector guidance; + FixedVector outPhase; + FixedVector mixdown; + FixedVector resampled; + ChannelAssembly(int channels) : + mag(channels, nullptr), phase(channels, nullptr), + prevMag(channels, nullptr), guidance(channels, nullptr), + outPhase(channels, nullptr), mixdown(channels, nullptr), + resampled(channels, nullptr) { } + }; + + struct ScaleData { + int fftSize; + FFT fft; + Window analysisWindow; + Window synthesisWindow; + double windowScaleFactor; + GuidedPhaseAdvance guided; + ScaleData(GuidedPhaseAdvance::Parameters guidedParameters, + Log log) : + fftSize(guidedParameters.fftSize), + fft(fftSize), + analysisWindow(analysisWindowShape(fftSize), + analysisWindowLength(fftSize)), + synthesisWindow(synthesisWindowShape(fftSize), + synthesisWindowLength(fftSize)), + windowScaleFactor(0.0), + guided(guidedParameters, log) + { + int asz = analysisWindow.getSize(), ssz = synthesisWindow.getSize(); + int off = (asz - ssz) / 2; + for (int i = 0; i < ssz; ++i) { + windowScaleFactor += analysisWindow.getValue(i + off) * + synthesisWindow.getValue(i); + } + } + + WindowType analysisWindowShape(int fftSize); + int analysisWindowLength(int fftSize); + WindowType synthesisWindowShape(int fftSize); + int synthesisWindowLength(int fftSize); + }; + + Parameters m_parameters; + Log m_log; + + std::atomic m_timeRatio; + std::atomic m_pitchScale; + std::atomic m_formantScale; + + std::vector> m_channelData; + std::map> m_scaleData; + Guide m_guide; + Guide::Configuration m_guideConfiguration; + ChannelAssembly m_channelAssembly; + std::unique_ptr m_calculator; + std::unique_ptr m_resampler; + std::atomic m_inhop; + int m_prevInhop; + int m_prevOuthop; + uint32_t m_unityCount; + int m_startSkip; + + size_t m_studyInputDuration; + size_t m_totalTargetDuration; + size_t m_processInputDuration; + size_t m_lastKeyFrameSurpassed; + size_t m_totalOutputDuration; + std::map m_keyFrameMap; + + enum class ProcessMode { + JustCreated, + Studying, + Processing, + Finished + }; + ProcessMode m_mode; + + void consume(); + void createResampler(); + void calculateHop(); + void updateRatioFromMap(); + void analyseChannel(int channel, int inhop, int prevInhop, int prevOuthop); + void analyseFormant(int channel); + void adjustFormant(int channel); + void adjustPreKick(int channel); + void synthesiseChannel(int channel, int outhop); + + struct ToPolarSpec { + int magFromBin; + int magBinCount; + int polarFromBin; + int polarBinCount; + }; + + void convertToPolar(double *mag, double *phase, + const double *real, const double *imag, + const ToPolarSpec &s) const { + v_cartesian_to_polar(mag + s.polarFromBin, + phase + s.polarFromBin, + real + s.polarFromBin, + imag + s.polarFromBin, + s.polarBinCount); + if (s.magFromBin < s.polarFromBin) { + v_cartesian_to_magnitudes(mag + s.magFromBin, + real + s.magFromBin, + imag + s.magFromBin, + s.polarFromBin - s.magFromBin); + } + if (s.magFromBin + s.magBinCount > s.polarFromBin + s.polarBinCount) { + v_cartesian_to_magnitudes(mag + s.polarFromBin + s.polarBinCount, + real + s.polarFromBin + s.polarBinCount, + imag + s.polarFromBin + s.polarBinCount, + s.magFromBin + s.magBinCount - + s.polarFromBin - s.polarBinCount); + } + } + + double getEffectiveRatio() const { + return m_timeRatio * m_pitchScale; + } + + bool isRealTime() const { + return m_parameters.options & + RubberBandStretcher::OptionProcessRealTime; + } +}; + +} + +#endif diff --git a/src/rubberband-c.cpp b/src/rubberband-c.cpp index d7e71d0..b5cca1b 100644 --- a/src/rubberband-c.cpp +++ b/src/rubberband-c.cpp @@ -53,6 +53,11 @@ void rubberband_reset(RubberBandState state) state->m_s->reset(); } +int rubberband_get_engine_version(RubberBandState state) +{ + return state->m_s->getEngineVersion(); +} + void rubberband_set_time_ratio(RubberBandState state, double ratio) { state->m_s->setTimeRatio(ratio); @@ -73,6 +78,16 @@ double rubberband_get_pitch_scale(const RubberBandState state) return state->m_s->getPitchScale(); } +void rubberband_set_formant_scale(RubberBandState state, double scale) +{ + state->m_s->setFormantScale(scale); +} + +double rubberband_get_formant_scale(const RubberBandState state) +{ + return state->m_s->getFormantScale(); +} + unsigned int rubberband_get_latency(const RubberBandState state) { return state->m_s->getLatency(); diff --git a/src/temporary.cpp b/src/temporary.cpp new file mode 100644 index 0000000..9d21171 --- /dev/null +++ b/src/temporary.cpp @@ -0,0 +1,8 @@ +#include "finer/R3StretcherImpl.h" + +int main(int argc, char **argv) +{ + RubberBand::R3StretcherImpl::Parameters parameters(44100.0, 2); + RubberBand::R3StretcherImpl impl(parameters); +} + diff --git a/src/test/TestAllocators.cpp b/src/test/TestAllocators.cpp new file mode 100644 index 0000000..71f0548 --- /dev/null +++ b/src/test/TestAllocators.cpp @@ -0,0 +1,106 @@ +/* -*- 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-2022 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 BOOST_TEST_DYN_LINK +#define BOOST_TEST_DYN_LINK +#endif +#include + +#include "../common/VectorOps.h" +#include "../common/Allocators.h" + +#include +#include + +using namespace RubberBand; + +BOOST_AUTO_TEST_SUITE(TestAllocators) + +#define COMPARE_ARRAY(a, b) \ + for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \ + BOOST_CHECK_SMALL(a[cmp_i] - b[cmp_i], 1e-14); \ + } + +#define COMPARE_N(a, b, n) \ + for (int cmp_i = 0; cmp_i < n; ++cmp_i) { \ + BOOST_CHECK_SMALL(a[cmp_i] - b[cmp_i], 1e-14); \ + } + +BOOST_AUTO_TEST_CASE(alloc_dealloc) +{ + double *v = allocate(4); + v[0] = 0.1; + v[1] = 2.0; + v[2] = -0.3; + v[3] = 4.0; + double *e = allocate(4); + e[0] = -0.3; + e[1] = 4.0; + e[2] = 0.1; + e[3] = 2.0; + v_fftshift(v, 4); + COMPARE_N(v, e, 4); + deallocate(v); + deallocate(e); +} + +BOOST_AUTO_TEST_CASE(alloc_zero) +{ + double *v = allocate_and_zero(4); + BOOST_CHECK_EQUAL(v[0], 0.f); + BOOST_CHECK_EQUAL(v[1], 0.f); + BOOST_CHECK_EQUAL(v[2], 0.f); + BOOST_CHECK_EQUAL(v[3], 0.f); + deallocate(v); +} + +BOOST_AUTO_TEST_CASE(alloc_dealloc_channels) +{ + double **v = allocate_channels(2, 4); + v[0][0] = 0.1; + v[0][1] = 2.0; + v[0][2] = -0.3; + v[0][3] = 4.0; + v[1][0] = -0.3; + v[1][1] = 4.0; + v[1][2] = 0.1; + v[1][3] = 2.0; + v_fftshift(v[0], 4); + COMPARE_N(v[0], v[1], 4); + deallocate_channels(v, 2); +} + +BOOST_AUTO_TEST_CASE(stl) +{ + std::vector > v; + v.push_back(0.1); + v.push_back(2.0); + v.push_back(-0.3); + v.push_back(4.0); + double e[] = { -0.3, 4.0, 0.1, 2.0 }; + v_fftshift(v.data(), 4); + COMPARE_N(v.data(), e, 4); +} + +BOOST_AUTO_TEST_SUITE_END() + diff --git a/src/test/TestBinClassifier.cpp b/src/test/TestBinClassifier.cpp new file mode 100644 index 0000000..303ffe7 --- /dev/null +++ b/src/test/TestBinClassifier.cpp @@ -0,0 +1,188 @@ +/* -*- 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-2022 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 BOOST_TEST_DYN_LINK +#define BOOST_TEST_DYN_LINK +#endif +#include + +// This test suite (shallowly) tests both BinClassifier and BinSegmenter +#include "../finer/BinClassifier.h" +#include "../finer/BinSegmenter.h" + +using namespace RubberBand; +using namespace std; +namespace tt = boost::test_tools; + +// We use the symbols H, X, and _ for harmonic, percussive, and +// residual respectively, because they are easier to distinguish than +// H, P, R + +static constexpr auto H = BinClassifier::Classification::Harmonic; +static constexpr auto X = BinClassifier::Classification::Percussive; +static constexpr auto _ = BinClassifier::Classification::Residual; + +vector classes_to_strings(const vector &v) +{ + vector sv(v.size(), "*"); + for (auto i = 0; i < int(v.size()); ++i) { + switch (v[i]) { + case H: sv[i] = "H"; break; + case X: sv[i] = "X"; break; + case _: sv[i] = "_"; break; + } + } + return sv; +} + +BOOST_AUTO_TEST_SUITE(TestBinClassifier) + +BOOST_AUTO_TEST_CASE(classify_bins) +{ + vector> magColumns { + { 0, 8, 1, 1, 0, 1 }, + { 0, 8, 0, 0, 0, 0 }, + { 8, 8, 8, 8, 8, 0 }, + { 0, 7, 0, 1, 0, 0 }, + { 0, 6, 0, 0, 0, 0 }, + { 0, 8, 0, 9, 9, 9 }, + { 0, 7, 0, 0, 1, 0 } + }; + + vector> classifications(7, { 6, _ }); + + BinClassifier::Parameters params(6, 3, /* lag */ 1, 3, 2.0, 2.0); + BinClassifier classifier(params); + + for (int i = 0; i < 7; ++i) { + classifier.classify(magColumns[i].data(), classifications[i].data()); + } + + /* + The lag of 1 specified for the horizontal filter means that the + results are delayed by a column (here row) but the vertical + filter outputs are aligned with the middle of the 3-bin + horizontal filters rather than the end. + + So the horizontal filter outputs (filtering vertically as + presented here) are + 0 8 1 1 0 1 <- This is the "lag" column that is not meaningful + 0 8 0 0 0 0 <- This is the actual median for the first col (row) + 0 8 1 1 0 0 + 0 8 0 1 0 0 + 0 7 0 1 0 0 + 0 7 0 1 0 0 + 0 7 0 0 1 0 + + And the vertical ones (lagged by one column to match the + horizontal filter outputs) are + 0 0 0 0 0 0 <- The "lag" column (here row) + 0 1 1 1 1 0 <- The effective first column (row) + 0 0 0 0 0 0 + 8 8 8 8 8 0 + 0 0 1 0 0 0 + 0 0 0 0 0 0 + 0 0 8 9 9 9 + + We have harmonic, percussive, and residual bins. (Initially we + detected silent bins too, but of course if done naively that + doesn't align with the lagged filter output, and silent bins + didn't appear relevant enough to take extra trouble over.) In + our case, wherever both horizontal and vertical filter outputs + are the same-ish (0, 1, or one of 7/8/9) we expect to see a + residual classification. Otherwise we expect harmonic if the + horizontal output is greater, percussive otherwise. + */ + + vector> expected { + // These results are lagged by one relative to the input + { _, H, H, H, _, H }, + { _, H, X, X, X, _ }, + { _, H, H, H, _, _ }, + { X, _, X, X, X, _ }, + { _, H, X, H, _, _ }, + { _, H, _, H, _, _ }, + { _, H, X, X, X, X } + }; + + for (int i = 0; i < 7; ++i) { + BOOST_TEST(classes_to_strings(classifications[i]) == + classes_to_strings(expected[i]), + tt::per_element()); + } +} + +BOOST_AUTO_TEST_CASE(segment_classification) +{ + vector> classification { + { _, H, X, X, X, _ }, + { _, H, H, H, _, _ }, + { X, _, X, X, X, _ }, + { _, H, X, H, _, _ }, + { X, X, _, H, _, _ }, + { _, H, X, X, X, X }, + { _, H, _, _, _, _ } + }; + + BinSegmenter::Parameters params(16, 6, 48000, 3); + BinSegmenter segmenter(params); + + vector segmented; + for (int i = 0; i < 7; ++i) { + segmented.push_back(segmenter.segment(classification[i].data())); + } + + /* + Modal filter length 3 was specified, with the ordering for + resolving equal counts as H, X, _. So the filtered + classifications will be: + + H H X X X X + H H H H _ _ + X X X X X X + H H H H _ _ + X X H _ _ _ + H H X X X X + H _ _ _ _ _ + */ + + vector expected { + { 0.0, 3000.0, 15000.0 }, + { 0.0, 9000.0, 9000.0 }, // Though any equal values would do! + { 0.0, 0.0, 15000.0 }, + { 0.0, 9000.0, 9000.0 }, + { 6000.0, 6000.0, 6000.0 }, // Similarly + { 0.0, 3000.0, 15000.0 }, + { 0.0, 24000.0, 24000.0 } + }; + + for (int i = 0; i < 7; ++i) { + BOOST_TEST(segmented[i].percussiveBelow == expected[i].percussiveBelow); + BOOST_TEST(segmented[i].percussiveAbove == expected[i].percussiveAbove); + BOOST_TEST(segmented[i].residualAbove == expected[i].residualAbove); + } +} + +BOOST_AUTO_TEST_SUITE_END() + + diff --git a/src/test/TestFFT.cpp b/src/test/TestFFT.cpp new file mode 100644 index 0000000..d915778 --- /dev/null +++ b/src/test/TestFFT.cpp @@ -0,0 +1,725 @@ +/* -*- 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-2022 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 BOOST_TEST_DYN_LINK +#define BOOST_TEST_DYN_LINK +#endif +#include + +#include "../common/FFT.h" + +#include + +#include +#include + +using namespace RubberBand; + +BOOST_AUTO_TEST_SUITE(TestFFT) + +#define DEFINE_EPS(fft) \ + float epsf = 1e-6f; \ + double eps; \ + if (fft.getSupportedPrecisions() & FFT::DoublePrecision) { \ + eps = 1e-14; \ + } else { \ + eps = epsf; \ + } \ + (void)epsf; (void)eps; + +#define USING_FFT(n) \ + FFT fft(n); \ + DEFINE_EPS(fft); + +#define COMPARE(a, b) BOOST_CHECK_SMALL(a-b, eps) +#define COMPARE_F(a, b) BOOST_CHECK_SMALL(a-b, epsf) + +#define COMPARE_ZERO(a) BOOST_CHECK_SMALL(a, eps) +#define COMPARE_ZERO_F(a) BOOST_CHECK_SMALL(a, epsf) + +#define COMPARE_ALL(a, x) \ + for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \ + BOOST_CHECK_SMALL(a[cmp_i] - x, eps); \ + } +#define COMPARE_ALL_F(a, x) \ + for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \ + BOOST_CHECK_SMALL(a[cmp_i] - x, epsf); \ + } +#define COMPARE_ARR(a, b, n) \ + for (int cmp_i = 0; cmp_i < n; ++cmp_i) { \ + BOOST_CHECK_SMALL(a[cmp_i] - b[cmp_i], eps); \ + } +#define COMPARE_SCALED(a, b, s) \ + for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \ + BOOST_CHECK_SMALL(a[cmp_i]/s - b[cmp_i], eps); \ + } +#define COMPARE_SCALED_N(a, b, n, s) \ + for (int cmp_i = 0; cmp_i < n; ++cmp_i) { \ + BOOST_CHECK_SMALL(a[cmp_i]/s - b[cmp_i], eps); \ + } +#define COMPARE_SCALED_F(a, b, s) \ + for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \ + BOOST_CHECK_SMALL(a[cmp_i]/s - b[cmp_i], epsf); \ + } + +#define ONE_IMPL_AUTO_TEST_CASE(name, impl) \ + BOOST_AUTO_TEST_CASE(name##_##impl) \ + { \ + std::set impls = FFT::getImplementations(); \ + if (impls.find(#impl) == impls.end()) return; \ + FFT::setDefaultImplementation(#impl); \ + performTest_##name(); \ + FFT::setDefaultImplementation(""); \ + } + +// If you add an implementation in FFT.cpp, add it also to +// ALL_IMPL_AUTO_TEST_CASE and all_implementations[] below + +#define ALL_IMPL_AUTO_TEST_CASE(name) \ + void performTest_##name (); \ + ONE_IMPL_AUTO_TEST_CASE(name, ipp); \ + ONE_IMPL_AUTO_TEST_CASE(name, vdsp); \ + ONE_IMPL_AUTO_TEST_CASE(name, fftw); \ + ONE_IMPL_AUTO_TEST_CASE(name, kissfft); \ + ONE_IMPL_AUTO_TEST_CASE(name, builtin); \ + ONE_IMPL_AUTO_TEST_CASE(name, dft); \ + void performTest_##name () + +std::string all_implementations[] = { + "ipp", "vdsp", "fftw", "kissfft", "builtin", "dft" +}; + +BOOST_AUTO_TEST_CASE(showImplementations) +{ + std::set impls = FFT::getImplementations(); + BOOST_TEST_MESSAGE("The following implementations are compiled in and will be tested:"); + for (int i = 0; i < int(sizeof(all_implementations)/sizeof(all_implementations[0])); ++i) { + if (impls.find(all_implementations[i]) != impls.end()) { + BOOST_TEST_MESSAGE(" +" << all_implementations[i]); + } + } + BOOST_TEST_MESSAGE("The following implementations are NOT compiled in and will not be tested:"); + for (int i = 0; i < int(sizeof(all_implementations)/sizeof(all_implementations[0])); ++i) { + if (impls.find(all_implementations[i]) == impls.end()) { + BOOST_TEST_MESSAGE(" -" << all_implementations[i]); + } + } +} + + +/* + * 1a. Simple synthetic signals, transforms to separate real/imag arrays, + * double-precision + */ + +ALL_IMPL_AUTO_TEST_CASE(dc) +{ + // DC-only signal. The DC bin is purely real + double in[] = { 1, 1, 1, 1 }; + double re[3], im[3]; + USING_FFT(4); + fft.forward(in, re, im); + COMPARE(re[0], 4.0); + COMPARE_ZERO(re[1]); + COMPARE_ZERO(re[2]); + COMPARE_ALL(im, 0.0); + double back[4]; + fft.inverse(re, im, back); + COMPARE_SCALED(back, in, 4); +} + +ALL_IMPL_AUTO_TEST_CASE(sine) +{ + // Sine. Output is purely imaginary + double in[] = { 0, 1, 0, -1 }; + double re[3], im[3]; + USING_FFT(4); + fft.forward(in, re, im); + COMPARE_ALL(re, 0.0); + COMPARE_ZERO(im[0]); + COMPARE(im[1], -2.0); + COMPARE_ZERO(im[2]); + double back[4]; + fft.inverse(re, im, back); + COMPARE_SCALED(back, in, 4); +} + +ALL_IMPL_AUTO_TEST_CASE(sine_8) +{ + // Longer sine. With only 4 elements, the real transform only + // needs to get the DC and Nyquist bins right for its two complex + // sub-transforms. We need a longer test to check the real + // transform is working properly. + double cospi4 = 0.5 * sqrt(2.0); + double in[] = { 0, cospi4, 1.0, cospi4, 0.0, -cospi4, -1.0, -cospi4 }; + double re[5], im[5]; + USING_FFT(8); + fft.forward(in, re, im); + COMPARE_ALL(re, 0.0); + COMPARE_ZERO(im[0]); + COMPARE(im[1], -4.0); + COMPARE_ZERO(im[2]); + COMPARE_ZERO(im[3]); + COMPARE_ZERO(im[4]); + double back[8]; + fft.inverse(re, im, back); + COMPARE_SCALED(back, in, 8); +} + +ALL_IMPL_AUTO_TEST_CASE(cosine) +{ + // Cosine. Output is purely real + double in[] = { 1, 0, -1, 0 }; + double re[3], im[3]; + USING_FFT(4); + fft.forward(in, re, im); + COMPARE_ZERO(re[0]); + COMPARE(re[1], 2.0); + COMPARE_ZERO(re[2]); + COMPARE_ALL(im, 0.0); + double back[4]; + fft.inverse(re, im, back); + COMPARE_SCALED(back, in, 4); +} + +ALL_IMPL_AUTO_TEST_CASE(cosine_8) +{ + // Longer cosine. + double cospi4 = 0.5 * sqrt(2.0); + double in[] = { 1.0, cospi4, 0.0, -cospi4, -1.0, -cospi4, 0.0, cospi4 }; + double re[5], im[5]; + USING_FFT(8); + fft.forward(in, re, im); + COMPARE_ALL(im, 0.0); + COMPARE_ZERO(re[0]); + COMPARE(re[1], 4.0); + COMPARE_ZERO(re[2]); + COMPARE_ZERO(re[3]); + COMPARE_ZERO(re[4]); + double back[8]; + fft.inverse(re, im, back); + COMPARE_SCALED(back, in, 8); +} + +ALL_IMPL_AUTO_TEST_CASE(sineCosine) +{ + // Sine and cosine mixed + double in[] = { 0.5, 1, -0.5, -1 }; + double re[3], im[3]; + USING_FFT(4); + fft.forward(in, re, im); + COMPARE_ZERO(re[0]); + COMPARE(re[1], 1.0); + COMPARE_ZERO(re[2]); + COMPARE_ZERO(im[0]); + COMPARE(im[1], -2.0); + COMPARE_ZERO(im[2]); + double back[4]; + fft.inverse(re, im, back); + COMPARE_SCALED(back, in, 4); +} + +ALL_IMPL_AUTO_TEST_CASE(nyquist) +{ + double in[] = { 1, -1, 1, -1 }; + double re[3], im[3]; + USING_FFT(4); + fft.forward(in, re, im); + COMPARE_ZERO(re[0]); + COMPARE_ZERO(re[1]); + COMPARE(re[2], 4.0); + COMPARE_ALL(im, 0.0); + double back[4]; + fft.inverse(re, im, back); + COMPARE_SCALED(back, in, 4); +} + +ALL_IMPL_AUTO_TEST_CASE(dirac) +{ + double in[] = { 1, 0, 0, 0 }; + double re[3], im[3]; + USING_FFT(4); + fft.forward(in, re, im); + COMPARE(re[0], 1.0); + COMPARE(re[1], 1.0); + COMPARE(re[2], 1.0); + COMPARE_ALL(im, 0.0); + double back[4]; + fft.inverse(re, im, back); + COMPARE_SCALED(back, in, 4); +} + + +/* + * 1b. Simple synthetic signals, transforms to separate real/imag arrays, + * single-precision (i.e. single-precision version of 1a) + */ + +ALL_IMPL_AUTO_TEST_CASE(dcF) +{ + // DC-only signal. The DC bin is purely real + float in[] = { 1, 1, 1, 1 }; + float re[3], im[3]; + USING_FFT(4); + fft.forward(in, re, im); + COMPARE_F(re[0], 4.0f); + COMPARE_ZERO_F(re[1]); + COMPARE_ZERO_F(re[2]); + COMPARE_ALL_F(im, 0.0f); + float back[4]; + fft.inverse(re, im, back); + COMPARE_SCALED_F(back, in, 4); +} + +ALL_IMPL_AUTO_TEST_CASE(sineF) +{ + // Sine. Output is purely imaginary + float in[] = { 0, 1, 0, -1 }; + float re[3], im[3]; + USING_FFT(4); + fft.forward(in, re, im); + COMPARE_ALL_F(re, 0.0f); + COMPARE_ZERO_F(im[0]); + COMPARE_F(im[1], -2.0f); + COMPARE_ZERO_F(im[2]); + float back[4]; + fft.inverse(re, im, back); + COMPARE_SCALED_F(back, in, 4); +} + +ALL_IMPL_AUTO_TEST_CASE(cosineF) +{ + // Cosine. Output is purely real + float in[] = { 1, 0, -1, 0 }; + float re[3], im[3]; + USING_FFT(4); + fft.forward(in, re, im); + COMPARE_ZERO_F(re[0]); + COMPARE_F(re[1], 2.0f); + COMPARE_ZERO_F(re[2]); + COMPARE_ALL_F(im, 0.0f); + float back[4]; + fft.inverse(re, im, back); + COMPARE_SCALED_F(back, in, 4); +} + +ALL_IMPL_AUTO_TEST_CASE(sineCosineF) +{ + // Sine and cosine mixed + float in[] = { 0.5, 1, -0.5, -1 }; + float re[3], im[3]; + USING_FFT(4); + fft.forward(in, re, im); + COMPARE_ZERO_F(re[0]); + COMPARE_F(re[1], 1.0f); + COMPARE_ZERO_F(re[2]); + COMPARE_ZERO_F(im[0]); + COMPARE_F(im[1], -2.0f); + COMPARE_ZERO_F(im[2]); + float back[4]; + fft.inverse(re, im, back); + COMPARE_SCALED_F(back, in, 4); +} + +ALL_IMPL_AUTO_TEST_CASE(nyquistF) +{ + float in[] = { 1, -1, 1, -1 }; + float re[3], im[3]; + USING_FFT(4); + fft.forward(in, re, im); + COMPARE_ZERO_F(re[0]); + COMPARE_ZERO_F(re[1]); + COMPARE_F(re[2], 4.0f); + COMPARE_ALL_F(im, 0.0f); + float back[4]; + fft.inverse(re, im, back); + COMPARE_SCALED_F(back, in, 4); +} + +ALL_IMPL_AUTO_TEST_CASE(diracF) +{ + float in[] = { 1, 0, 0, 0 }; + float re[3], im[3]; + USING_FFT(4); + fft.forward(in, re, im); + COMPARE_F(re[0], 1.0f); + COMPARE_F(re[1], 1.0f); + COMPARE_F(re[2], 1.0f); + COMPARE_ALL_F(im, 0.0f); + float back[4]; + fft.inverse(re, im, back); + COMPARE_SCALED_F(back, in, 4); +} + + +/* + * 2a. Subset of synthetic signals, testing different output formats + * (interleaved complex, polar, magnitude-only, and our weird + * cepstral thing), double-precision + */ + +ALL_IMPL_AUTO_TEST_CASE(interleaved) +{ + // Sine and cosine mixed, test output format + double in[] = { 0.5, 1, -0.5, -1 }; + double out[6]; + USING_FFT(4); + fft.forwardInterleaved(in, out); + COMPARE_ZERO(out[0]); + COMPARE_ZERO(out[1]); + COMPARE(out[2], 1.0); + COMPARE(out[3], -2.0); + COMPARE_ZERO(out[4]); + COMPARE_ZERO(out[5]); + double back[4]; + fft.inverseInterleaved(out, back); + COMPARE_SCALED(back, in, 4); +} + +ALL_IMPL_AUTO_TEST_CASE(sinePolar) +{ + double in[] = { 0, 1, 0, -1 }; + double mag[3], phase[3]; + USING_FFT(4); + fft.forwardPolar(in, mag, phase); + COMPARE_ZERO(mag[0]); + COMPARE(mag[1], 2.0); + COMPARE_ZERO(mag[2]); + // No meaningful tests for phase[i] where mag[i]==0 (phase + // could legitimately be anything) + COMPARE(phase[1], -M_PI/2.0); + double back[4]; + fft.inversePolar(mag, phase, back); + COMPARE_SCALED(back, in, 4); +} + +ALL_IMPL_AUTO_TEST_CASE(cosinePolar) +{ + double in[] = { 1, 0, -1, 0 }; + double mag[3], phase[3]; + USING_FFT(4); + fft.forwardPolar(in, mag, phase); + COMPARE_ZERO(mag[0]); + COMPARE(mag[1], 2.0); + COMPARE_ZERO(mag[2]); + // No meaningful tests for phase[i] where mag[i]==0 (phase + // could legitimately be anything) + COMPARE_ZERO(phase[1]); + double back[4]; + fft.inversePolar(mag, phase, back); + COMPARE_SCALED(back, in, 4); +} + +ALL_IMPL_AUTO_TEST_CASE(magnitude) +{ + // Sine and cosine mixed + double in[] = { 0.5, 1, -0.5, -1 }; + double out[3]; + USING_FFT(4); + fft.forwardMagnitude(in, out); + COMPARE_ZERO(out[0]); + COMPARE_F(float(out[1]), sqrtf(5.0)); + COMPARE_ZERO(out[2]); +} + +ALL_IMPL_AUTO_TEST_CASE(cepstrum) +{ + double in[] = { 1, 0, 0, 0, 1, 0, 0, 0 }; + double mag[5]; + USING_FFT(8); + fft.forwardMagnitude(in, mag); + double cep[8]; + fft.inverseCepstral(mag, cep); + BOOST_CHECK_SMALL(cep[1], 1e-9); + BOOST_CHECK_SMALL(cep[2], 1e-9); + BOOST_CHECK_SMALL(cep[3], 1e-9); + BOOST_CHECK_SMALL(cep[5], 1e-9); + BOOST_CHECK_SMALL(cep[6], 1e-9); + BOOST_CHECK_SMALL(cep[7], 1e-9); + BOOST_CHECK_SMALL(-6.561181 - cep[0]/8, 0.000001); + BOOST_CHECK_SMALL( 7.254329 - cep[4]/8, 0.000001); +} + + +/* + * 2b. Subset of synthetic signals, testing different output formats + * (interleaved complex, polar, magnitude-only, and our weird + * cepstral thing), single-precision (i.e. single-precision + * version of 2a) + */ + +ALL_IMPL_AUTO_TEST_CASE(interleavedF) +{ + // Sine and cosine mixed, test output format + float in[] = { 0.5, 1, -0.5, -1 }; + float out[6]; + USING_FFT(4); + fft.forwardInterleaved(in, out); + COMPARE_ZERO_F(out[0]); + COMPARE_ZERO_F(out[1]); + COMPARE_F(out[2], 1.0f); + COMPARE_F(out[3], -2.0f); + COMPARE_ZERO_F(out[4]); + COMPARE_ZERO_F(out[5]); + float back[4]; + fft.inverseInterleaved(out, back); + COMPARE_SCALED_F(back, in, 4); +} + +ALL_IMPL_AUTO_TEST_CASE(cosinePolarF) +{ + float in[] = { 1, 0, -1, 0 }; + float mag[3], phase[3]; + USING_FFT(4); + fft.forwardPolar(in, mag, phase); + COMPARE_ZERO_F(mag[0]); + COMPARE_F(mag[1], 2.0f); + COMPARE_ZERO_F(mag[2]); + // No meaningful tests for phase[i] where mag[i]==0 (phase + // could legitimately be anything) + COMPARE_ZERO_F(phase[1]); + float back[4]; + fft.inversePolar(mag, phase, back); + COMPARE_SCALED_F(back, in, 4); +} + +ALL_IMPL_AUTO_TEST_CASE(sinePolarF) +{ + float in[] = { 0, 1, 0, -1 }; + float mag[3], phase[3]; + USING_FFT(4); + fft.forwardPolar(in, mag, phase); + COMPARE_ZERO_F(mag[0]); + COMPARE_F(mag[1], 2.0f); + COMPARE_ZERO_F(mag[2]); + // No meaningful tests for phase[i] where mag[i]==0 (phase + // could legitimately be anything) + COMPARE_F(phase[1], -float(M_PI)/2.0f); + float back[4]; + fft.inversePolar(mag, phase, back); + COMPARE_SCALED_F(back, in, 4); +} + +ALL_IMPL_AUTO_TEST_CASE(magnitudeF) +{ + // Sine and cosine mixed + float in[] = { 0.5, 1, -0.5, -1 }; + float out[3]; + USING_FFT(4); + fft.forwardMagnitude(in, out); + COMPARE_ZERO_F(out[0]); + COMPARE_F(float(out[1]), sqrtf(5.0f)); + COMPARE_ZERO_F(out[2]); +} + +ALL_IMPL_AUTO_TEST_CASE(cepstrumF) +{ + float in[] = { 1, 0, 0, 0, 1, 0, 0, 0 }; + float mag[5]; + USING_FFT(8); + fft.forwardMagnitude(in, mag); + float cep[8]; + fft.inverseCepstral(mag, cep); + COMPARE_ZERO_F(cep[1]); + COMPARE_ZERO_F(cep[2]); + COMPARE_ZERO_F(cep[3]); + COMPARE_ZERO_F(cep[5]); + COMPARE_ZERO_F(cep[6]); + COMPARE_ZERO_F(cep[7]); + BOOST_CHECK_SMALL(-6.561181 - cep[0]/8, 0.000001); + BOOST_CHECK_SMALL( 7.254329 - cep[4]/8, 0.000001); +} + + +/* + * 4. Bounds checking, double-precision and single-precision + */ + +ALL_IMPL_AUTO_TEST_CASE(forwardArrayBounds) +{ + double in[] = { 1, 1, -1, -1 }; + + // Initialise output bins to something recognisable, so we can + // tell if they haven't been written + double re[] = { 999, 999, 999, 999, 999 }; + double im[] = { 999, 999, 999, 999, 999 }; + + USING_FFT(4); + fft.forward(in, re+1, im+1); + + // Check we haven't overrun the output arrays + COMPARE(re[0], 999.0); + COMPARE(im[0], 999.0); + COMPARE(re[4], 999.0); + COMPARE(im[4], 999.0); +} + +ALL_IMPL_AUTO_TEST_CASE(inverseArrayBounds) +{ + // The inverse transform is only supposed to refer to the first + // N/2+1 bins and synthesise the rest rather than read them - so + // initialise the next one to some value that would mess up the + // results if it were used + double re[] = { 0, 1, 0, 456 }; + double im[] = { 0, -2, 0, 456 }; + + // Initialise output bins to something recognisable, so we can + // tell if they haven't been written + double out[] = { 999, 999, 999, 999, 999, 999 }; + + USING_FFT(4); + fft.inverse(re, im, out+1); + + // Check we haven't overrun the output arrays + COMPARE(out[0], 999.0); + COMPARE(out[5], 999.0); + + // And check the results are as we expect, i.e. that we haven't + // used the bogus final bin + COMPARE(out[1] / 4, 0.5); + COMPARE(out[2] / 4, 1.0); + COMPARE(out[3] / 4, -0.5); + COMPARE(out[4] / 4, -1.0); +} + +ALL_IMPL_AUTO_TEST_CASE(forwardArrayBoundsF) +{ + float in[] = { 1, 1, -1, -1 }; + + // Initialise output bins to something recognisable, so we can + // tell if they haven't been written + float re[] = { 999, 999, 999, 999, 999 }; + float im[] = { 999, 999, 999, 999, 999 }; + + USING_FFT(4); + fft.forward(in, re+1, im+1); + + // Check we haven't overrun the output arrays + COMPARE_F(re[0], 999.0f); + COMPARE_F(im[0], 999.0f); + COMPARE_F(re[4], 999.0f); + COMPARE_F(im[4], 999.0f); +} + +ALL_IMPL_AUTO_TEST_CASE(inverseArrayBoundsF) +{ + // The inverse transform is only supposed to refer to the first + // N/2+1 bins and synthesise the rest rather than read them - so + // initialise the next one to some value that would mess up the + // results if it were used + float re[] = { 0, 1, 0, 456 }; + float im[] = { 0, -2, 0, 456 }; + + // Initialise output bins to something recognisable, so we can + // tell if they haven't been written + float out[] = { 999, 999, 999, 999, 999, 999 }; + + USING_FFT(4); + fft.inverse(re, im, out+1); + + // Check we haven't overrun the output arrays + COMPARE_F(out[0], 999.0f); + COMPARE_F(out[5], 999.0f); + + // And check the results are as we expect, i.e. that we haven't + // used the bogus final bin + COMPARE_F(out[1] / 4.0f, 0.5f); + COMPARE_F(out[2] / 4.0f, 1.0f); + COMPARE_F(out[3] / 4.0f, -0.5f); + COMPARE_F(out[4] / 4.0f, -1.0f); +} + + +/* + * 6. Slightly longer transforms of pseudorandom data. + */ + +ALL_IMPL_AUTO_TEST_CASE(random_precalc_16) +{ + double in[] = { + -0.24392125308057722, 0.03443898163344272, 0.3448145656738877, + -0.9625837464603908, 3.366568317669671, 0.9947191221586653, + -1.5038984435999945, 1.3859898682581235, -1.1230576306688778, + -1.6757487116512024, -1.5874436867863229, -2.0794018781307155, + -0.5450152775818973, 0.7530907176983748, 1.0743170685904255, + 3.1787609811018775 + }; + double expected_re[] = { + 1.41162899482, 7.63975551593, -1.20622641052, -1.77829578443, + 3.12678465246, -2.84220463109, -7.17083743716, 0.497290409945, + -1.84690167439, + }; + double expected_im[] = { + 0.0, -4.67826048083, 8.58829211964, 4.96449646815, + 1.41626511493, -3.77219223978, 6.96219662744, 2.23138519225, + 0.0, + }; + double re[9], im[9]; + USING_FFT(16); + if (eps < 1e-11) { + eps = 1e-11; + } + fft.forward(in, re, im); + COMPARE_ARR(re, expected_re, 9); + COMPARE_ARR(im, expected_im, 9); + double back[16]; + fft.inverse(re, im, back); + COMPARE_SCALED(back, in, 16); +} + +/* This one has data from a PRNG, with a fixed seed. Must pass two + * tests: (i) same as DFT; (ii) inverse produces original input (after + * scaling) */ +ALL_IMPL_AUTO_TEST_CASE(random) +{ + const int n = 64; + double *in = new double[n]; + double *re = new double[n/2 + 1]; + double *im = new double[n/2 + 1]; + double *re_compare = new double[n/2 + 1]; + double *im_compare = new double[n/2 + 1]; + double *back = new double[n]; + srand48(0); + for (int i = 0; i < n; ++i) { + in[i] = drand48() * 4.0 - 2.0; + } + USING_FFT(n); + if (eps < 1e-11) { + eps = 1e-11; + } + fft.forward(in, re, im); + fft.inverse(re, im, back); + FFT::setDefaultImplementation("dft"); + fft.forward(in, re_compare, im_compare); + COMPARE_ARR(re, re_compare, n/2 + 1); + COMPARE_ARR(im, im_compare, n/2 + 1); + COMPARE_SCALED_N(back, in, n, n); + delete[] back; + delete[] im_compare; + delete[] re_compare; + delete[] im; + delete[] re; + delete[] in; +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/TestResampler.cpp b/src/test/TestResampler.cpp new file mode 100644 index 0000000..b7b5bb3 --- /dev/null +++ b/src/test/TestResampler.cpp @@ -0,0 +1,220 @@ +/* -*- 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-2022 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 BOOST_TEST_DYN_LINK +#define BOOST_TEST_DYN_LINK +#endif +#include + +#include "../common/Resampler.h" + +#include +#include +#include +#include + +using namespace std; +using namespace RubberBand; + +BOOST_AUTO_TEST_SUITE(TestResampler) + +#define LEN(a) (int(sizeof(a)/sizeof(a[0]))) + +static vector +sine(double samplerate, double frequency, int nsamples) +{ + vector v(nsamples, 0.f); + for (int i = 0; i < nsamples; ++i) { + v[i] = sin ((i * 2.0 * M_PI * frequency) / samplerate); + } + return v; +} + +#define COMPARE_N(a, b, n) \ + for (int cmp_i = 0; cmp_i < n; ++cmp_i) { \ + BOOST_CHECK_SMALL((a)[cmp_i] - (b)[cmp_i], 1e-4f); \ + } + +static const float guard_value = -999.f; + +BOOST_AUTO_TEST_CASE(interpolated_sine_1ch_interleaved) +{ + // Interpolating a sinusoid should give us a sinusoid, once we've + // dropped the first few samples + vector in = sine(8, 2, 1000); // 2Hz wave at 8Hz: [ 0, 1, 0, -1 ] etc + vector expected = sine(16, 2, 2000); + vector out(in.size() * 2 + 1, guard_value); + Resampler r(Resampler::Parameters(), 1); + int returned = r.resampleInterleaved + (out.data(), out.size(), in.data(), in.size(), 2, true); + + // because final was true, we expect to have exactly the right + // number of samples back + BOOST_CHECK_EQUAL(returned, int(in.size() * 2)); + BOOST_CHECK_NE(out[returned-1], guard_value); + BOOST_CHECK_EQUAL(out[returned], guard_value); + + // and they should match the expected data, at least in the middle + const float *outf = out.data() + 200, *expectedf = expected.data() + 200; + COMPARE_N(outf, expectedf, 600); +} + +BOOST_AUTO_TEST_CASE(interpolated_sine_1ch_noninterleaved) +{ + // Interpolating a sinusoid should give us a sinusoid, once we've + // dropped the first few samples + vector in = sine(8, 2, 1000); // 2Hz wave at 8Hz: [ 0, 1, 0, -1 ] etc + vector expected = sine(16, 2, 2000); + vector out(in.size() * 2 + 1, guard_value); + const float *in_data = in.data(); + float *out_data = out.data(); + Resampler r(Resampler::Parameters(), 1); + int returned = r.resample + (&out_data, out.size(), &in_data, in.size(), 2, true); + + // because final was true, we expect to have exactly the right + // number of samples back + BOOST_CHECK_EQUAL(returned, int(in.size() * 2)); + BOOST_CHECK_NE(out[returned-1], guard_value); + BOOST_CHECK_EQUAL(out[returned], guard_value); + + // and they should match the expected data, at least in the middle + const float *outf = out.data() + 200, *expectedf = expected.data() + 200; + COMPARE_N(outf, expectedf, 600); +} + +BOOST_AUTO_TEST_CASE(overrun_interleaved) +{ + // Check that the outcount argument is correctly used: any samples + // already in the out buffer beyond outcount*channels must be left + // untouched. We test this with short buffers (likely to be + // shorter than the resampler filter length) and longer ones, with + // resampler ratios that reduce, leave unchanged, and raise the + // sample rate, and with all quality settings. + + // Options are ordered from most normal/likely to least. + + int channels = 2; + + int lengths[] = { 6000, 6 }; + int constructionBufferSizes[] = { 0, 1000 }; + double ratios[] = { 1.0, 10.0, 0.1 }; + Resampler::Quality qualities[] = { + Resampler::FastestTolerable, Resampler::Best, Resampler::Fastest + }; + + bool failed = false; + + for (int li = 0; li < LEN(lengths); ++li) { + for (int cbi = 0; cbi < LEN(constructionBufferSizes); ++cbi) { + for (int ri = 0; ri < LEN(ratios); ++ri) { + for (int qi = 0; qi < LEN(qualities); ++qi) { + + int length = lengths[li]; + double ratio = ratios[ri]; + Resampler::Parameters parameters; + parameters.quality = qualities[qi]; + parameters.maxBufferSize = constructionBufferSizes[cbi]; + Resampler r(parameters, channels); + + float *inbuf = new float[length * channels]; + for (int i = 0; i < length; ++i) { + for (int c = 0; c < channels; ++c) { + inbuf[i*channels+c] = + sinf((i * 2.0 * M_PI * 440.0) / 44100.0); + } + } + + int outcount = int(round(length * ratio)); + outcount -= 10; + if (outcount < 1) outcount = 1; + int outbuflen = outcount + 10; + float *outbuf = new float[outbuflen * channels]; + for (int i = 0; i < outbuflen; ++i) { + for (int c = 0; c < channels; ++c) { + outbuf[i*channels+c] = guard_value; + } + } +/* + cerr << "\nTesting with length = " << length << ", ratio = " + << ratio << ", outcount = " << outcount << ", final = false" + << endl; +*/ + int returned = r.resampleInterleaved + (outbuf, outcount, inbuf, length, ratio, false); + + BOOST_CHECK_LE(returned, outcount); + + for (int i = returned; i < outbuflen; ++i) { + for (int c = 0; c < channels; ++c) { + BOOST_CHECK_EQUAL(outbuf[i*channels+c], guard_value); + if (outbuf[i*channels+c] != guard_value) { + failed = true; + } + } + } + + if (failed) { + cerr << "Test failed, abandoning remaining loop cycles" + << endl; + break; + } +/* + cerr << "\nContinuing with length = " << length << ", ratio = " + << ratio << ", outcount = " << outcount << ", final = true" + << endl; +*/ + returned = r.resampleInterleaved + (outbuf, outcount, inbuf, length, ratio, true); + + BOOST_CHECK_LE(returned, outcount); + + for (int i = returned; i < outbuflen; ++i) { + for (int c = 0; c < channels; ++c) { + BOOST_CHECK_EQUAL(outbuf[i*channels+c], guard_value); + if (outbuf[i*channels+c] != guard_value) { + failed = true; + } + } + } + + delete[] outbuf; + delete[] inbuf; + + if (failed) { + cerr << "Test failed, abandoning remaining loop cycles" + << endl; + break; + } + } + + if (failed) break; + } + if (failed) break; + } + if (failed) break; + } +} + +BOOST_AUTO_TEST_SUITE_END() + diff --git a/src/test/TestSignalBits.cpp b/src/test/TestSignalBits.cpp new file mode 100644 index 0000000..70c073f --- /dev/null +++ b/src/test/TestSignalBits.cpp @@ -0,0 +1,348 @@ +/* -*- 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-2022 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 BOOST_TEST_DYN_LINK +#define BOOST_TEST_DYN_LINK +#endif +#include + +#include "../common/MovingMedian.h" +#include "../common/HistogramFilter.h" +#include "../finer/Peak.h" + +using namespace RubberBand; +using namespace std; +namespace tt = boost::test_tools; + +BOOST_AUTO_TEST_SUITE(TestSignalBits) + +BOOST_AUTO_TEST_CASE(moving_median_simple_3) +{ + MovingMedian mm(3); + vector arr { 1.0, 2.0, 3.0 }; + vector expected { 1.0, 2.0, 2.0 }; + MovingMedian::filter(mm, arr); + BOOST_TEST(arr == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(moving_median_simple_4) +{ + MovingMedian mm(4); + vector arr { 1.0, 2.0, 3.0, 4.0 }; + vector expected { 2.0, 2.0, 3.0, 3.0 }; + MovingMedian::filter(mm, arr); + BOOST_TEST(arr == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(moving_median_simple_3_4) +{ + MovingMedian mm(3); + vector arr { 1.2, 0.6, 1.0e-6, 1.0 }; + vector expected { 0.6, 0.6, 0.6, 1.0e-6 }; + MovingMedian::filter(mm, arr); + BOOST_TEST(arr == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(moving_median_simple_5_4) +{ + MovingMedian mm(5); + vector arr { 1.2, 0.6, 1.0e-6, 1.0 }; + vector expected { 0.6, 0.6, 0.6, 0.6 }; + MovingMedian::filter(mm, arr); + BOOST_TEST(arr == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(moving_median_order_1) +{ + MovingMedian mm(1); + vector arr { 1.2, 0.6, 1.0e-6, 1.0e-6 }; + vector expected = arr; + MovingMedian::filter(mm, arr); + BOOST_TEST(arr == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(moving_median_n_1) +{ + MovingMedian mm(6); + vector arr { 1.0 }; + vector expected { 1.0 }; + MovingMedian::filter(mm, arr); + BOOST_TEST(arr == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(histogram_median_simple_3) +{ + HistogramFilter hf(5, 3); // nValues, filterLength + vector arr { 1, 2, 3 }; + vector expected { 1, 2, 2 }; + HistogramFilter::medianFilter(hf, arr); + BOOST_TEST(arr == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(histogram_median_simple_4) +{ + HistogramFilter hf(5, 4); // nValues, filterLength + vector arr { 1, 2, 3, 4 }; + vector expected { 2, 2, 3, 3 }; + HistogramFilter::medianFilter(hf, arr); + BOOST_TEST(arr == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(histogram_median_simple_3_4) +{ + HistogramFilter hf(5, 3); // nValues, filterLength + vector arr { 3, 1, 0, 2 }; + vector expected { 1, 1, 1, 0 }; + HistogramFilter::medianFilter(hf, arr); + BOOST_TEST(arr == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(histogram_median_simple_5_4) +{ + HistogramFilter hf(5, 5); + vector arr { 3, 1, 0, 2 }; + vector expected { 1, 1, 1, 1 }; + HistogramFilter::medianFilter(hf, arr); + BOOST_TEST(arr == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(histogram_median_order_1) +{ + HistogramFilter hf(4, 1); + vector arr { 3, 1, 0, 0 }; + vector expected = arr; + HistogramFilter::medianFilter(hf, arr); + BOOST_TEST(arr == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(histogram_median_n_1) +{ + HistogramFilter hf(3, 6); + vector arr { 1 }; + vector expected { 1 }; + HistogramFilter::medianFilter(hf, arr); + BOOST_TEST(arr == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(histogram_mode_simple_3) +{ + HistogramFilter hf(5, 3); // nValues, filterLength + vector arr { 1, 2, 2 }; + vector expected { 1, 2, 2 }; + HistogramFilter::modalFilter(hf, arr); + BOOST_TEST(arr == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(histogram_mode_simple_4) +{ + HistogramFilter hf(5, 4); // nValues, filterLength + vector arr { 1, 2, 2, 4 }; + vector expected { 2, 2, 2, 2 }; + HistogramFilter::modalFilter(hf, arr); + BOOST_TEST(arr == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(histogram_mode_simple_3_4) +{ + HistogramFilter hf(5, 3); // nValues, filterLength + vector arr { 3, 1, 0, 0 }; + vector expected { 1, 0, 0, 0 }; + HistogramFilter::modalFilter(hf, arr); + BOOST_TEST(arr == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(histogram_mode_simple_5_4) +{ + HistogramFilter hf(5, 5); + vector arr { 3, 1, 0, 0 }; + vector expected { 0, 0, 0, 0 }; + HistogramFilter::modalFilter(hf, arr); + BOOST_TEST(arr == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(histogram_mode_order_1) +{ + HistogramFilter hf(4, 1); + vector arr { 3, 1, 0, 0 }; + vector expected = arr; + HistogramFilter::modalFilter(hf, arr); + BOOST_TEST(arr == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(histogram_mode_n_1) +{ + HistogramFilter hf(3, 6); + vector arr { 1 }; + vector expected { 1 }; + HistogramFilter::modalFilter(hf, arr); + BOOST_TEST(arr == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(peakpick_nearest_2_1) +{ + Peak pp(1); + vector in { -0.1 }; + vector out(1); + vector expected { 0 }; + pp.findNearestAndNextPeaks(in.data(), 2, out.data(), nullptr); + BOOST_TEST(out == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(peakpick_nearest_2_5) +{ + Peak pp(5); + vector in { -0.3, -0.1, -0.2, 1.0, -0.3 }; + vector out(5); + vector expected { 3, 3, 3, 3, 3 }; + pp.findNearestAndNextPeaks(in.data(), 2, out.data(), nullptr); + BOOST_TEST(out == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(peakpick_nearest_2_12) +{ + Peak pp(12); + vector in { -0.3, -0.1, -0.2, 1.0, -0.3, -0.5, + -0.5, -0.4, -0.1, -0.1, -0.2, -0.3 }; + vector out(12); + vector expected { 3, 3, 3, 3, 3, 3, 8, 8, 8, 8, 8, 8 }; + pp.findNearestAndNextPeaks(in.data(), 2, out.data(), nullptr); + BOOST_TEST(out == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(peakpick_nearest_1_12) +{ + Peak pp(12); + vector in { -0.3, -0.1, -0.2, 1.0, -0.3, -0.5, + -0.5, -0.4, -0.1, -0.1, -0.2, -0.3 }; + vector out(12); + vector expected { 1, 1, 3, 3, 3, 3, 8, 8, 8, 8, 8, 8 }; + pp.findNearestAndNextPeaks(in.data(), 1, out.data(), nullptr); + BOOST_TEST(out == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(peakpick_nearest_0_12) +{ + Peak pp(12); + vector in { -0.3, -0.1, -0.2, 1.0, -0.3, -0.5, + -0.5, -0.4, -0.1, -0.1, -0.2, -0.3 }; + vector out(12); + vector expected { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; + pp.findNearestAndNextPeaks(in.data(), 0, out.data(), nullptr); + BOOST_TEST(out == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(peakpick_next_2_1) +{ + Peak pp(1); + vector in { -0.1 }; + vector out(1); + vector expected { 0 }; + pp.findNearestAndNextPeaks(in.data(), 2, nullptr, out.data()); + BOOST_TEST(out == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(peakpick_next_2_5) +{ + Peak pp(5); + vector in { -0.3, -0.1, -0.2, 1.0, -0.3 }; + vector out(5); + vector expected { 3, 3, 3, 3, 4 }; + pp.findNearestAndNextPeaks(in.data(), 2, nullptr, out.data()); + BOOST_TEST(out == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(peakpick_next_2_12) +{ + Peak pp(12); + vector in { -0.3, -0.1, 0.2, -1.0, -0.3, -0.5, + -0.5, -0.4, -0.1, -0.1, -0.2, -0.3 }; + vector out(12); + vector expected { 2, 2, 2, 8, 8, 8, 8, 8, 8, 9, 10, 11 }; + pp.findNearestAndNextPeaks(in.data(), 2, nullptr, out.data()); + BOOST_TEST(out == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(troughpick_nearest_2_1) +{ + Peak> pp(1); + vector in { -0.1 }; + vector out(1); + vector expected { 0 }; + pp.findNearestAndNextPeaks(in.data(), 2, out.data(), nullptr); + BOOST_TEST(out == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(troughpick_nearest_2_5) +{ + Peak> pp(5); + vector in { -0.3, -0.1, -0.4, 0.1, -0.3 }; + vector out(5); + vector expected { 2, 2, 2, 2, 2 }; + pp.findNearestAndNextPeaks(in.data(), 2, out.data(), nullptr); + BOOST_TEST(out == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(troughpick_nearest_2_12) +{ + Peak> pp(12); + vector in { -0.3, -0.1, -0.2, 1.0, -0.3, -0.5, + -0.5, -0.4, -0.1, -0.1, -0.2, -0.3 }; + vector out(12); + vector expected { 0, 0, 0, 5, 5, 5, 5, 5, 11, 11, 11, 11 }; + pp.findNearestAndNextPeaks(in.data(), 2, out.data(), nullptr); + BOOST_TEST(out == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(troughpick_next_2_1) +{ + Peak> pp(1); + vector in { -0.1 }; + vector out(1); + vector expected { 0 }; + pp.findNearestAndNextPeaks(in.data(), 2, nullptr, out.data()); + BOOST_TEST(out == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(troughpick_next_2_5) +{ + Peak> pp(5); + vector in { -0.3, -0.1, -0.4, 0.1, -0.3 }; + vector out(5); + vector expected { 2, 2, 2, 3, 4 }; + pp.findNearestAndNextPeaks(in.data(), 2, nullptr, out.data()); + BOOST_TEST(out == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(troughpick_next_2_12) +{ + Peak> pp(12); + vector in { -0.3, -0.1, 0.2, 1.0, -0.3, -0.5, + -0.5, -0.4, -0.1, -0.1, -0.2, -0.3 }; + vector out(12); + vector expected { 0, 5, 5, 5, 5, 5, 11, 11, 11, 11, 11, 11 }; + pp.findNearestAndNextPeaks(in.data(), 2, nullptr, out.data()); + BOOST_TEST(out == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_SUITE_END() + diff --git a/src/test/TestStretchCalculator.cpp b/src/test/TestStretchCalculator.cpp new file mode 100644 index 0000000..ae48056 --- /dev/null +++ b/src/test/TestStretchCalculator.cpp @@ -0,0 +1,128 @@ +/* -*- 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-2022 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 BOOST_TEST_DYN_LINK +#define BOOST_TEST_DYN_LINK +#endif +#include + +#include "../common/StretchCalculator.h" + +#include + +using namespace RubberBand; +using namespace std; +namespace tt = boost::test_tools; + +static Log cerrLog( + [](const char *message) { + cerr << message << "\n"; + }, + [](const char *message, double arg) { + cerr << message << ": " << arg << "\n"; + }, + [](const char *message, double arg0, double arg1) { + cerr << message << ": (" << arg0 << ", " << arg1 << ")\n"; + } + ); + +BOOST_AUTO_TEST_SUITE(TestStretchCalculator) + +BOOST_AUTO_TEST_CASE(offline_linear_hp) +{ + StretchCalculator sc(44100, 512, true, cerrLog); + + vector out, expected; + + out = sc.calculate(1.0, 5120, { 1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0 }); + expected = { 512, 512, 512, 512, 512, + 512, 512, 512, 512, 512 }; + BOOST_TEST(out == expected, tt::per_element()); + + out = sc.calculate(0.5, 5120, { 1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0 }); + expected = { 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256 }; + BOOST_TEST(out == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(offline_linear_nohp) +{ + StretchCalculator sc(44100, 512, false, cerrLog); + + vector out, expected; + + out = sc.calculate(1.0, 5120, { 1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0 }); + expected = { 512, 512, 512, 512, 512, + 512, 512, 512, 512, 512 }; + BOOST_TEST(out == expected, tt::per_element()); + + out = sc.calculate(0.5, 5120, { 1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0 }); + expected = { 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256 }; + BOOST_TEST(out == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(offline_onep_hp) +{ + StretchCalculator sc(44100, 512, true, cerrLog); + + vector out, expected; + + out = sc.calculate(1.0, 5120, { 1.0, 1.0, 1.0, 1.0, 2.0, + 1.0, 1.0, 1.0, 1.0, 1.0 }); + expected = { 512, 512, 512, 512, -512, + 512, 512, 512, 512, 512 }; + BOOST_TEST(out == expected, tt::per_element()); + + out = sc.calculate(0.5, 5120, { 1.0, 1.0, 1.0, 1.0, 2.0, + 1.0, 1.0, 1.0, 1.0, 1.0 }); + expected = { 256, 256, 256, 256, -512, + 205, 205, 204, 205, 205 }; + BOOST_TEST(out == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(offline_onep_nohp) +{ + StretchCalculator sc(44100, 512, false, cerrLog); + + vector out, expected; + + out = sc.calculate(1.0, 5120, { 1.0, 1.0, 1.0, 1.0, 2.0, + 1.0, 1.0, 1.0, 1.0, 1.0 }); + expected = { 512, 512, 512, 512, 512, + 512, 512, 512, 512, 512 }; + BOOST_TEST(out == expected, tt::per_element()); + + out = sc.calculate(0.5, 5120, { 1.0, 1.0, 1.0, 1.0, 2.0, + 1.0, 1.0, 1.0, 1.0, 1.0 }); + expected = { 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256 }; + BOOST_TEST(out == expected, tt::per_element()); +} + +BOOST_AUTO_TEST_SUITE_END() + diff --git a/src/test/TestStretcher.cpp b/src/test/TestStretcher.cpp new file mode 100644 index 0000000..37fb569 --- /dev/null +++ b/src/test/TestStretcher.cpp @@ -0,0 +1,436 @@ +/* -*- 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-2022 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 BOOST_TEST_DYN_LINK +#define BOOST_TEST_DYN_LINK +#endif +#include + +#include "../../rubberband/RubberBandStretcher.h" + +#include + +#include + +using namespace RubberBand; +using namespace std; +namespace tt = boost::test_tools; + +BOOST_AUTO_TEST_SUITE(TestStretcher) + +BOOST_AUTO_TEST_CASE(engine_version) +{ + RubberBandStretcher s2(44100, 1, RubberBandStretcher::OptionEngineFaster); + BOOST_TEST(s2.getEngineVersion() == 2); + RubberBandStretcher s3(44100, 1, RubberBandStretcher::OptionEngineFiner); + BOOST_TEST(s3.getEngineVersion() == 3); +} + +BOOST_AUTO_TEST_CASE(sinusoid_unchanged_offline_faster) +{ + int n = 10000; + float freq = 440.f; + int rate = 44100; + RubberBandStretcher stretcher + (rate, 1, RubberBandStretcher::OptionEngineFaster); + + vector in(n), out(n); + for (int i = 0; i < n; ++i) { + in[i] = sinf(float(i) * freq * M_PI * 2.f / float(rate)); + } + float *inp = in.data(), *outp = out.data(); + + stretcher.setMaxProcessSize(n); + stretcher.setExpectedInputDuration(n); + BOOST_TEST(stretcher.available() == 0); + + stretcher.study(&inp, n, true); + BOOST_TEST(stretcher.available() == 0); + + stretcher.process(&inp, n, true); + BOOST_TEST(stretcher.available() == n); + + BOOST_TEST(stretcher.getLatency() == 0); // offline mode + + size_t got = stretcher.retrieve(&outp, n); + BOOST_TEST(got == n); + BOOST_TEST(stretcher.available() == -1); + + // We now have n samples of a simple sinusoid with stretch factor + // 1.0; obviously we expect the output to be essentially the same + // thing. It will have lower precision for a while at the start + // and end because of windowing factors, so we check those with a + // threshold of 0.1; in the middle we expect better + // precision. Note that these are relative tolerances, not + // absolute, i.e. 0.001 means 0.001x the smaller value - so they + // are tighter than they appear. + + // This syntax for comparing containers with a certain tolerance + // using BOOST_TEST is just bonkers. I can't find the << syntax to + // combine manipulators documented anywhere other than in a + // release note, but it does work. Well, sort of - it works this + // way around but not as per_element << tolerance. And + // tolerance(0.1) doesn't do what you'd expect if the things + // you're comparing are floats (it sets the tolerance for doubles, + // leaving float comparison unchanged). Clever... too clever. + + BOOST_TEST(out == in, + tt::tolerance(0.1f) << tt::per_element()); + + BOOST_TEST(vector(out.begin() + 1024, out.begin() + n - 1024) == + vector(in.begin() + 1024, in.begin() + n - 1024), + tt::tolerance(0.001f) << tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(sinusoid_unchanged_offline_finer) +{ + int n = 10000; + float freq = 440.f; + int rate = 44100; + + RubberBandStretcher stretcher + (rate, 1, RubberBandStretcher::OptionEngineFiner); + + vector in(n), out(n); + for (int i = 0; i < n; ++i) { + in[i] = sinf(float(i) * freq * M_PI * 2.f / float(rate)); + } + float *inp = in.data(), *outp = out.data(); + + stretcher.setMaxProcessSize(n); + stretcher.setExpectedInputDuration(n); + BOOST_TEST(stretcher.available() == 0); + + stretcher.study(&inp, n, true); + BOOST_TEST(stretcher.available() == 0); + + stretcher.process(&inp, n, true); + BOOST_TEST(stretcher.available() == n); + + BOOST_TEST(stretcher.getLatency() == 0); // offline mode + + size_t got = stretcher.retrieve(&outp, n); + BOOST_TEST(got == n); + BOOST_TEST(stretcher.available() == -1); + + // The R3 engine is actually less precise than R2 here because of + // its different windowing design, though see the note above about + // what these tolerances mean + + BOOST_TEST(out == in, + tt::tolerance(0.35f) << tt::per_element()); + + BOOST_TEST(vector(out.begin() + 1024, out.begin() + n - 1024) == + vector(in.begin() + 1024, in.begin() + n - 1024), + tt::tolerance(0.01f) << tt::per_element()); + +// std::cout << "ms\tV" << std::endl; +// for (int i = 0; i < n; ++i) { +// std::cout << i << "\t" << out[i] - in[i] << std::endl; +// } +} + +BOOST_AUTO_TEST_CASE(sinusoid_2x_offline_finer) +{ + int n = 10000; + float freq = 441.f; // so a cycle is an exact number of samples + int rate = 44100; + + RubberBandStretcher stretcher + (rate, 1, RubberBandStretcher::OptionEngineFiner); + + stretcher.setTimeRatio(2.0); + + vector in(n), out(n*2); + for (int i = 0; i < n*2; ++i) { + out[i] = sinf(float(i) * freq * M_PI * 2.f / float(rate)); + if (i < n) { + in[i] = out[i]; + } + } + float *inp = in.data(), *outp = out.data(); + + stretcher.setMaxProcessSize(n); + stretcher.setExpectedInputDuration(n); + BOOST_TEST(stretcher.available() == 0); + + stretcher.study(&inp, n, true); + BOOST_TEST(stretcher.available() == 0); + + stretcher.process(&inp, n, true); + BOOST_TEST(stretcher.available() == n*2); + + BOOST_TEST(stretcher.getLatency() == 0); // offline mode + + size_t got = stretcher.retrieve(&outp, n*2); + BOOST_TEST(got == n*2); + BOOST_TEST(stretcher.available() == -1); + + int period = -1; + for (int i = 1000; i < 2000; ++i) { + if (period >= 0) ++period; + if (out[i] <= 0.f && out[i+1] > 0.f) { + if (period == -1) period = 0; + else break; + } + } + BOOST_TEST(period == 100); + + int offset = 0; + for (int i = 0; i < 200; ++i) { + if (out[i] <= 0.f && out[i+1] > -0.01f) { + offset = i + 1; + break; + } + } + + // overall + + double rms = 0.0; + for (int i = 0; i < n - offset; ++i) { + double diff = out[i + offset] - in[i]; + rms += diff * diff; + } + rms = sqrt(rms / double(n - offset)); + BOOST_TEST(rms < 0.2); + + // steady state + + rms = 0.0; + for (int i = 1500; i < n - offset - 3000; ++i) { + double diff = out[i + offset + 1500] - in[i + 1500]; + rms += diff * diff; + } + rms = sqrt(rms / double(n - offset - 3000)); + BOOST_TEST(rms < 0.1); +} + +BOOST_AUTO_TEST_CASE(impulses_2x_offline_faster) +{ + int n = 10000; + int rate = 44100; + RubberBandStretcher stretcher + (rate, 1, RubberBandStretcher::OptionEngineFaster); + + stretcher.setTimeRatio(2.0); + + vector in(n, 0.f), out(n * 2, 0.f); + + in[100] = 1.f; + in[101] = -1.f; + + in[5000] = 1.f; + in[5001] = -1.f; + + in[9900] = 1.f; + in[9901] = -1.f; + + float *inp = in.data(), *outp = out.data(); + + stretcher.setMaxProcessSize(n); + stretcher.setExpectedInputDuration(n); + BOOST_TEST(stretcher.available() == 0); + + stretcher.study(&inp, n, true); + BOOST_TEST(stretcher.available() == 0); + + stretcher.process(&inp, n, true); + BOOST_TEST(stretcher.available() == n * 2); + + BOOST_TEST(stretcher.getLatency() == 0); // offline mode + + size_t got = stretcher.retrieve(&outp, n * 2); + BOOST_TEST(got == n * 2); + BOOST_TEST(stretcher.available() == -1); + + int peak0 = -1, peak1 = -1, peak2 = -1; + float max; + + max = -2.f; + for (int i = 0; i < n/2; ++i) { + if (out[i] > max) { max = out[i]; peak0 = i; } + } + + max = -2.f; + for (int i = n/2; i < (n*3)/2; ++i) { + if (out[i] > max) { max = out[i]; peak1 = i; } + } + + max = -2.f; + for (int i = (n*3)/2; i < n*2; ++i) { + if (out[i] > max) { max = out[i]; peak2 = i; } + } + + BOOST_TEST(peak0 == 100); + BOOST_TEST(peak1 > n - 400); + BOOST_TEST(peak1 < n + 50); + BOOST_TEST(peak2 > n*2 - 600); + BOOST_TEST(peak2 < n*2); +/* + std::cout << "ms\tV" << std::endl; + for (int i = 0; i < n*2; ++i) { + std::cout << i << "\t" << out[i] << std::endl; + } +*/ +} + +BOOST_AUTO_TEST_CASE(impulses_2x_offline_finer) +{ + int n = 10000; + int rate = 44100; + RubberBandStretcher stretcher + (rate, 1, RubberBandStretcher::OptionEngineFiner); + + stretcher.setTimeRatio(2.0); + + vector in(n, 0.f), out(n * 2, 0.f); + + in[100] = 1.f; + in[101] = -1.f; + + in[5000] = 1.f; + in[5001] = -1.f; + + in[9900] = 1.f; + in[9901] = -1.f; + + float *inp = in.data(), *outp = out.data(); + + stretcher.setMaxProcessSize(n); + stretcher.setExpectedInputDuration(n); + BOOST_TEST(stretcher.available() == 0); + + stretcher.study(&inp, n, true); + BOOST_TEST(stretcher.available() == 0); + + stretcher.process(&inp, n, true); + BOOST_TEST(stretcher.available() == n * 2); + + BOOST_TEST(stretcher.getLatency() == 0); // offline mode + + size_t got = stretcher.retrieve(&outp, n * 2); + BOOST_TEST(got == n * 2); + BOOST_TEST(stretcher.available() == -1); + + int peak0 = -1, peak1 = -1, peak2 = -1; + float max; + + max = -2.f; + for (int i = 0; i < n/2; ++i) { + if (out[i] > max) { max = out[i]; peak0 = i; } + } + + max = -2.f; + for (int i = n/2; i < (n*3)/2; ++i) { + if (out[i] > max) { max = out[i]; peak1 = i; } + } + + max = -2.f; + for (int i = (n*3)/2; i < n*2; ++i) { + if (out[i] > max) { max = out[i]; peak2 = i; } + } + + BOOST_TEST(peak0 == 100); + BOOST_TEST(peak1 > n - 400); + BOOST_TEST(peak1 < n + 50); + BOOST_TEST(peak2 > n*2 - 600); + BOOST_TEST(peak2 < n*2); +/* + std::cout << "ms\tV" << std::endl; + for (int i = 0; i < n*2; ++i) { + std::cout << i << "\t" << out[i] << std::endl; + } +*/ +} + +BOOST_AUTO_TEST_CASE(impulses_2x_5up_offline_finer) +{ + int n = 10000; + int rate = 44100; + RubberBandStretcher stretcher + (rate, 1, RubberBandStretcher::OptionEngineFiner); + + stretcher.setTimeRatio(2.0); + stretcher.setPitchScale(1.5); + + vector in(n, 0.f), out(n * 2, 0.f); + + in[100] = 1.f; + in[101] = -1.f; + + in[5000] = 1.f; + in[5001] = -1.f; + + in[9900] = 1.f; + in[9901] = -1.f; + + float *inp = in.data(), *outp = out.data(); + + stretcher.setMaxProcessSize(n); + stretcher.setExpectedInputDuration(n); + BOOST_TEST(stretcher.available() == 0); + + stretcher.study(&inp, n, true); + BOOST_TEST(stretcher.available() == 0); + + stretcher.process(&inp, n, true); + BOOST_TEST(stretcher.available() == n * 2); + + BOOST_TEST(stretcher.getLatency() == 0); // offline mode + + size_t got = stretcher.retrieve(&outp, n * 2); + BOOST_TEST(got == n * 2); + BOOST_TEST(stretcher.available() == -1); + + int peak0 = -1, peak1 = -1, peak2 = -1; + float max; + + max = -2.f; + for (int i = 0; i < n/2; ++i) { + if (out[i] > max) { max = out[i]; peak0 = i; } + } + + max = -2.f; + for (int i = n/2; i < (n*3)/2; ++i) { + if (out[i] > max) { max = out[i]; peak1 = i; } + } + + max = -2.f; + for (int i = (n*3)/2; i < n*2; ++i) { + if (out[i] > max) { max = out[i]; peak2 = i; } + } + + BOOST_TEST(peak0 < 100); + BOOST_TEST(peak1 > n - 400); + BOOST_TEST(peak1 < n + 50); + BOOST_TEST(peak2 > n*2 - 600); + BOOST_TEST(peak2 < n*2); +/* + std::cout << "ms\tV" << std::endl; + for (int i = 0; i < n*2; ++i) { + std::cout << i << "\t" << out[i] << std::endl; + } +*/ +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/TestVectorOps.cpp b/src/test/TestVectorOps.cpp new file mode 100644 index 0000000..8f2803f --- /dev/null +++ b/src/test/TestVectorOps.cpp @@ -0,0 +1,251 @@ +/* -*- 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-2022 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 BOOST_TEST_DYN_LINK +#define BOOST_TEST_DYN_LINK +#endif +#include + +#include "../common/VectorOps.h" + +#include +#include + +using namespace RubberBand; + +BOOST_AUTO_TEST_SUITE(TestVectorOps) + +#define COMPARE_ARRAY(a, b) \ + for (int cmp_i = 0; cmp_i < (int)(sizeof(a)/sizeof(a[0])); ++cmp_i) { \ + BOOST_CHECK_SMALL(a[cmp_i] - b[cmp_i], 1e-14); \ + } + +#define COMPARE_N(a, b, n) \ + for (int cmp_i = 0; cmp_i < n; ++cmp_i) { \ + BOOST_CHECK_SMALL(a[cmp_i] - b[cmp_i], 1e-14); \ + } + +BOOST_AUTO_TEST_CASE(add) +{ + double a[] = { 1.0, 2.0, 3.0 }; + double b[] = { -1.0, 3.0, -4.5 }; + double expected[] = { 0.0, 5.0, -1.5 }; + v_add(a, b, 3); + COMPARE_N(a, expected, 3); +} + +BOOST_AUTO_TEST_CASE(add_with_gain) +{ + double a[] = { 1.0, 2.0, 3.0 }; + double b[] = { -1.0, 3.0, -4.5 }; + double expected[] = { -0.5, 6.5, -3.75 }; + v_add_with_gain(a, b, 1.5, 3); + COMPARE_N(a, expected, 3); +} + +BOOST_AUTO_TEST_CASE(subtract) +{ + double a[] = { 1.0, 2.0, 3.0 }; + double b[] = { -1.0, 3.0, -4.5 }; + double expected[] = { 2.0, -1.0, 7.5 }; + v_subtract(a, b, 3); + COMPARE_N(a, expected, 3); +} + +BOOST_AUTO_TEST_CASE(scale) +{ + double a[] = { -1.0, 3.0, -4.5 }; + double scale = -0.5; + double expected[] = { 0.5, -1.5, 2.25 }; + v_scale(a, scale, 3); + COMPARE_N(a, expected, 3); +} + +BOOST_AUTO_TEST_CASE(multiply) +{ + double a[] = { 1.0, 2.0, 3.0 }; + double b[] = { -1.0, 3.0, -4.5 }; + double expected[] = { -1.0, 6.0, -13.5 }; + v_multiply(a, b, 3); + COMPARE_N(a, expected, 3); +} + +BOOST_AUTO_TEST_CASE(multiply_and_add) +{ + double a[] = { 1.0, 2.0, 3.0 }; + double b[] = { -1.0, 3.0, -4.5 }; + double c[] = { 3.0, -1.0, 4.0 }; + double expected[] = { 2.0, 5.0, -9.5 }; + v_multiply_and_add(c, a, b, 3); + COMPARE_N(c, expected, 3); +} + +BOOST_AUTO_TEST_CASE(divide) +{ + double a[] = { 1.0, 2.0, 3.0 }; + double b[] = { -1.0, 3.0, -4.5 }; + double expected[] = { -1.0, 2.0/3.0, 3.0/-4.5 }; + v_divide(a, b, 3); + COMPARE_N(a, expected, 3); +} + +BOOST_AUTO_TEST_CASE(sum) +{ + double a[] = { 1.0, 2.0, -3.5 }; + double s = v_sum(a, 3); + BOOST_CHECK_EQUAL(s, -0.5); +} + +BOOST_AUTO_TEST_CASE(multiply_and_sum) +{ + double a[] = { 2.0, 0.0, -1.5 }; + double b[] = { 3.0, 4.0, 5.0 }; + double s = v_multiply_and_sum(a, b, 3); + BOOST_CHECK_EQUAL(s, -1.5); +} + +BOOST_AUTO_TEST_CASE(log) +{ + double a[] = { 1.0, 1.0 / M_E, M_E }; + double expected[] = { 0.0, -1.0, 1.0 }; + v_log(a, 3); + COMPARE_N(a, expected, 3); +} + +BOOST_AUTO_TEST_CASE(exp) +{ + double a[] = { 0.0, -1.0, 2.0 }; + double expected[] = { 1.0, 1.0 / M_E, M_E * M_E }; + v_exp(a, 3); + COMPARE_N(a, expected, 3); +} + +BOOST_AUTO_TEST_CASE(sqrt) +{ + double a[] = { 0.0, 1.0, 4.0 }; + double expected[] = { 0.0, 1.0, 2.0 }; + v_sqrt(a, 3); + COMPARE_N(a, expected, 3); +} + +BOOST_AUTO_TEST_CASE(square) +{ + double a[] = { 0.0, 1.5, -2.0 }; + double expected[] = { 0.0, 2.25, 4.0 }; + v_square(a, 3); + COMPARE_N(a, expected, 3); +} + +BOOST_AUTO_TEST_CASE(abs) +{ + double a[] = { -1.9, 0.0, 0.01, -0.0 }; + double expected[] = { 1.9, 0.0, 0.01, 0.0 }; + v_abs(a, 4); + COMPARE_N(a, expected, 4); +} + +BOOST_AUTO_TEST_CASE(mean) +{ + double a[] = { -1.0, 1.6, 3.0 }; + double s = v_mean(a, 3); + BOOST_CHECK_EQUAL(s, 1.2); +} + +BOOST_AUTO_TEST_CASE(interleave_1) +{ + double a[] = { 1.0, 2.0, 3.0 }; + double *ch[] = { a }; + double o[3]; + double expected[] = { 1.0, 2.0, 3.0 }; + v_interleave(o, ch, 1, 3); + COMPARE_N(o, expected, 3); +} + +BOOST_AUTO_TEST_CASE(interleave_2) +{ + double a[] = { 1.0, 2.0, 3.0 }; + double b[] = { 4.0, 5.0, 6.0 }; + double *ch[] = { a, b }; + double o[6]; + double expected[] = { 1.0, 4.0, 2.0, 5.0, 3.0, 6.0 }; + v_interleave(o, ch, 2, 3); + COMPARE_N(o, expected, 6); +} + +BOOST_AUTO_TEST_CASE(interleave_3) +{ + double a[] = { 1.0, 2.0 }; + double b[] = { 3.0, 4.0 }; + double c[] = { 5.0, 6.0 }; + double *ch[] = { a, b, c }; + double o[6]; + double expected[] = { 1.0, 3.0, 5.0, 2.0, 4.0, 6.0 }; + v_interleave(o, ch, 3, 2); + COMPARE_N(o, expected, 6); +} + +BOOST_AUTO_TEST_CASE(deinterleave_1) +{ + double a[] = { 1.0, 2.0, 3.0 }; + double o[3]; + double *oo[] = { o }; + double *expected[] = { a }; + v_deinterleave(oo, a, 1, 3); + COMPARE_N(oo[0], expected[0], 3); +} + +BOOST_AUTO_TEST_CASE(deinterleave_2) +{ + double a[] = { 1.0, 4.0, 2.0, 5.0, 3.0, 6.0 }; + double o1[3], o2[3]; + double *oo[] = { o1, o2 }; + double e1[] = { 1.0, 2.0, 3.0 }, e2[] = { 4.0, 5.0, 6.0 }; + double *expected[] = { e1, e2 }; + v_deinterleave(oo, a, 2, 3); + COMPARE_N(oo[0], expected[0], 3); + COMPARE_N(oo[1], expected[1], 3); +} + +BOOST_AUTO_TEST_CASE(deinterleave_3) +{ + double a[] = { 1.0, 3.0, 5.0, 2.0, 4.0, 6.0 }; + double o1[2], o2[2], o3[2]; + double *oo[] = { o1, o2, o3 }; + double e1[] = { 1.0, 2.0 }, e2[] = { 3.0, 4.0 }, e3[] = { 5.0, 6.0 }; + double *expected[] = { e1, e2, e3 }; + v_deinterleave(oo, a, 3, 2); + COMPARE_N(oo[0], expected[0], 2); + COMPARE_N(oo[1], expected[1], 2); + COMPARE_N(oo[2], expected[2], 2); +} + +BOOST_AUTO_TEST_CASE(fftshift) +{ + double a[] = { 0.1, 2.0, -0.3, 4.0 }; + double e[] = { -0.3, 4.0, 0.1, 2.0 }; + v_fftshift(a, 4); + COMPARE_N(a, e, 4); +} + +BOOST_AUTO_TEST_SUITE_END() + diff --git a/src/test/TestVectorOpsComplex.cpp b/src/test/TestVectorOpsComplex.cpp new file mode 100644 index 0000000..0eebf5d --- /dev/null +++ b/src/test/TestVectorOpsComplex.cpp @@ -0,0 +1,136 @@ +/* -*- 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-2022 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 BOOST_TEST_DYN_LINK +#define BOOST_TEST_DYN_LINK +#endif +#include + +#include "../common/VectorOpsComplex.h" +#include "../common/VectorOps.h" + +#include +#include +#include + +using namespace RubberBand; + +using namespace std; + +BOOST_AUTO_TEST_SUITE(TestVectorOpsComplex) + +#ifdef USE_APPROXIMATE_ATAN2 +static const double eps = 5.0e-3; +static const double eps_approx = eps; +#else +static const double eps = 1.0e-14; +static const double eps_approx = 1.0e-8; +#endif + +#define COMPARE_N(a, b, n) \ + for (int cmp_i = 0; cmp_i < n; ++cmp_i) { \ + BOOST_CHECK_SMALL(a[cmp_i] - b[cmp_i], eps); \ + } + +BOOST_AUTO_TEST_CASE(cartesian_to_magnitudes) +{ + double re[] = { 1.0, 3.0 }; + double im[] = { 2.0, -4.0 }; + double o[2]; + double expected[] = { sqrt(5.0), 5.0 }; + v_cartesian_to_magnitudes(o, re, im, 2); + COMPARE_N(o, expected, 2); +} + +BOOST_AUTO_TEST_CASE(cartesian_interleaved_to_magnitudes) +{ + double a[] = { 1.0, 2.0, 3.0, -4.0 }; + double o[2]; + double expected[] = { sqrt(5.0), 5.0 }; + v_cartesian_interleaved_to_magnitudes(o, a, 2); + COMPARE_N(o, expected, 2); +} + +BOOST_AUTO_TEST_CASE(cartesian_to_polar) +{ + double re[] = { 0.0, 1.0, 0.0 }; + double im[] = { 0.0, 1.0, -1.0 }; + double mo[3], po[3]; + double me[] = { 0.0, sqrt(2.0), 1.0 }; + double pe[] = { 0.0, M_PI / 4.0, -M_PI * 0.5 }; + v_cartesian_to_polar(mo, po, re, im, 3); + COMPARE_N(mo, me, 3); + COMPARE_N(po, pe, 3); +} + +BOOST_AUTO_TEST_CASE(cartesian_to_polar_interleaved_inplace) +{ + double a[] = { 0.0, 0.0, 1.0, 1.0, 0.0, -1.0 }; + double e[] = { 0.0, 0.0, sqrt(2.0), M_PI / 4.0, 1.0, -M_PI * 0.5 }; + v_cartesian_to_polar_interleaved_inplace(a, 3); + COMPARE_N(a, e, 6); +} + +BOOST_AUTO_TEST_CASE(cartesian_interleaved_to_polar) +{ + double a[] = { 0.0, 0.0, 1.0, 1.0, 0.0, -1.0 }; + double mo[3], po[3]; + double me[] = { 0.0, sqrt(2.0), 1.0 }; + double pe[] = { 0.0, M_PI / 4.0, -M_PI * 0.5 }; + v_cartesian_interleaved_to_polar(mo, po, a, 3); + COMPARE_N(mo, me, 3); + COMPARE_N(po, pe, 3); +} + +BOOST_AUTO_TEST_CASE(polar_to_cartesian) +{ + double m[] = { 0.0, sqrt(2.0), 1.0 }; + double p[] = { 0.0, M_PI / 4.0, -M_PI * 0.5 }; + double ro[3], io[3]; + double re[] = { 0.0, 1.0, 0.0 }; + double ie[] = { 0.0, 1.0, -1.0 }; + v_polar_to_cartesian(ro, io, m, p, 3); + COMPARE_N(ro, re, 3); + COMPARE_N(io, ie, 3); +} + +BOOST_AUTO_TEST_CASE(polar_to_cartesian_interleaved_inplace) +{ + double a[] = { 0.0, 0.0, sqrt(2.0), M_PI / 4.0, 1.0, -M_PI * 0.5 }; + double e[] = { 0.0, 0.0, 1.0, 1.0, 0.0, -1.0 }; + v_polar_interleaved_to_cartesian_inplace(a, 3); + COMPARE_N(a, e, 6); +} + +BOOST_AUTO_TEST_CASE(polar_to_cartesian_interleaved) +{ + double m[] = { 0.0, sqrt(2.0), 1.0 }; + double p[] = { 0.0, M_PI / 4.0, -M_PI * 0.5 }; + double o[6]; + double e[] = { 0.0, 0.0, 1.0, 1.0, 0.0, -1.0 }; + v_polar_to_cartesian_interleaved(o, m, p, 3); + COMPARE_N(o, e, 6); +} + +BOOST_AUTO_TEST_SUITE_END() + diff --git a/src/audiocurves/SpectralDifferenceAudioCurve.h b/src/test/test.cpp similarity index 58% rename from src/audiocurves/SpectralDifferenceAudioCurve.h rename to src/test/test.cpp index c78383d..b290077 100644 --- a/src/audiocurves/SpectralDifferenceAudioCurve.h +++ b/src/test/test.cpp @@ -21,34 +21,8 @@ you must obtain a valid commercial licence before doing so. */ -#ifndef RUBBERBAND_SPECTRALDIFFERENCE_AUDIO_CURVE_H -#define RUBBERBAND_SPECTRALDIFFERENCE_AUDIO_CURVE_H - -#include "../dsp/AudioCurveCalculator.h" -#include "../dsp/Window.h" - -namespace RubberBand -{ - -class SpectralDifferenceAudioCurve : public AudioCurveCalculator -{ -public: - SpectralDifferenceAudioCurve(Parameters parameters); - - virtual ~SpectralDifferenceAudioCurve(); - - virtual void setFftSize(int newSize); - - virtual float processFloat(const float *R__ mag, int increment); - virtual double processDouble(const double *R__ mag, int increment); - virtual void reset(); - virtual const char *getUnit() const { return "V"; } - -protected: - double *R__ m_mag; - double *R__ m_tmpbuf; -}; - -} - +#define BOOST_TEST_MODULE RubberBand +#ifndef BOOST_TEST_DYN_LINK +#define BOOST_TEST_DYN_LINK #endif +#include diff --git a/vamp/RubberBandVampPlugin.cpp b/vamp/RubberBandVampPlugin.cpp index 00f2840..028ee81 100644 --- a/vamp/RubberBandVampPlugin.cpp +++ b/vamp/RubberBandVampPlugin.cpp @@ -23,8 +23,8 @@ #include "RubberBandVampPlugin.h" -#include "StretchCalculator.h" -#include "system/sysutils.h" +#include "common/StretchCalculator.h" +#include "common/sysutils.h" #include #include @@ -454,6 +454,28 @@ RubberBandVampPlugin::Impl::processOffline(const float *const *inputBuffers, return FeatureSet(); } +static RubberBand::Log makeCerrLog() +{ + auto log0 = [](const char *message) { + std::cerr << "RubberBand: " << message << "\n"; + }; + auto log1 = [](const char *message, double arg0) { + auto prec = std::cerr.precision(); + std::cerr.precision(10); + std::cerr << "RubberBand: " << message << ": " << arg0 << "\n"; + std::cerr.precision(prec); + }; + auto log2 = [](const char *message, double arg0, double arg1) { + auto prec = std::cerr.precision(); + std::cerr.precision(10); + std::cerr << "RubberBand: " << message + << ": (" << arg0 << ", " << arg1 << ")" << "\n"; + std::cerr.precision(prec); + }; + + return RubberBand::Log(log0, log1, log2); +} + RubberBandVampPlugin::FeatureSet RubberBandVampPlugin::Impl::getRemainingFeaturesOffline() { @@ -463,7 +485,8 @@ RubberBandVampPlugin::Impl::getRemainingFeaturesOffline() int rate = m_sampleRate; - RubberBand::StretchCalculator sc(rate, m_stretcher->getInputIncrement(), true); + RubberBand::StretchCalculator sc + (rate, m_stretcher->getInputIncrement(), true, makeCerrLog()); size_t inputIncrement = m_stretcher->getInputIncrement(); std::vector outputIncrements = m_stretcher->getOutputIncrements();