From 80473f8735ba2cebc053c1274e70f5eace2c9bfe Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 18 May 2022 14:12:57 +0100 Subject: [PATCH 001/184] Remove elastic/loose processing support --- main/main.cpp | 21 +- meson.build | 2 - rubberband/RubberBandStretcher.h | 29 +- rubberband/rubberband-c.h | 4 +- src/StretchCalculator.cpp | 364 ++---------------- src/StretchCalculator.h | 17 +- src/StretcherImpl.cpp | 52 +-- src/StretcherImpl.h | 15 +- src/StretcherProcess.cpp | 22 +- src/audiocurves/ConstantAudioCurve.cpp | 57 --- src/audiocurves/ConstantAudioCurve.h | 45 --- .../SpectralDifferenceAudioCurve.cpp | 107 ----- .../SpectralDifferenceAudioCurve.h | 54 --- 13 files changed, 79 insertions(+), 710 deletions(-) delete mode 100644 src/audiocurves/ConstantAudioCurve.cpp delete mode 100644 src/audiocurves/ConstantAudioCurve.h delete mode 100644 src/audiocurves/SpectralDifferenceAudioCurve.cpp delete mode 100644 src/audiocurves/SpectralDifferenceAudioCurve.h diff --git a/main/main.cpp b/main/main.cpp index 78e841d..90f75dc 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -90,7 +90,7 @@ int main(int argc, char **argv) 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; @@ -179,8 +179,8 @@ 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; @@ -276,9 +276,10 @@ int main(int argc, char **argv) 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 << " -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 << " -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; @@ -294,6 +295,8 @@ int main(int argc, char **argv) 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; @@ -330,6 +333,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; @@ -493,7 +501,6 @@ int main(int argc, char **argv) RubberBandStretcher::Options options = 0; 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; diff --git a/meson.build b/meson.build index 3f9a9ec..679b897 100644 --- a/meson.build +++ b/meson.build @@ -38,10 +38,8 @@ library_sources = [ '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', diff --git a/rubberband/RubberBandStretcher.h b/rubberband/RubberBandStretcher.h index 6e963b1..b216048 100644 --- a/rubberband/RubberBandStretcher.h +++ b/rubberband/RubberBandStretcher.h @@ -109,27 +109,6 @@ 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. - * - * \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. - * * 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 @@ -293,6 +272,10 @@ 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 { @@ -300,8 +283,8 @@ public: OptionProcessOffline = 0x00000000, OptionProcessRealTime = 0x00000001, - OptionStretchElastic = 0x00000000, - OptionStretchPrecise = 0x00000010, + OptionStretchElastic = 0x00000000, // obsolete + OptionStretchPrecise = 0x00000010, // obsolete OptionTransientsCrisp = 0x00000000, OptionTransientsMixed = 0x00000100, diff --git a/rubberband/rubberband-c.h b/rubberband/rubberband-c.h index 6c3a4e9..5172c26 100644 --- a/rubberband/rubberband-c.h +++ b/rubberband/rubberband-c.h @@ -57,8 +57,8 @@ enum RubberBandOption { RubberBandOptionProcessOffline = 0x00000000, RubberBandOptionProcessRealTime = 0x00000001, - RubberBandOptionStretchElastic = 0x00000000, - RubberBandOptionStretchPrecise = 0x00000010, + RubberBandOptionStretchElastic = 0x00000000, // obsolete + RubberBandOptionStretchPrecise = 0x00000010, // obsolete RubberBandOptionTransientsCrisp = 0x00000000, RubberBandOptionTransientsMixed = 0x00000100, diff --git a/src/StretchCalculator.cpp b/src/StretchCalculator.cpp index 2e04de0..da228a3 100644 --- a/src/StretchCalculator.cpp +++ b/src/StretchCalculator.cpp @@ -74,11 +74,8 @@ StretchCalculator::setKeyFrameMap(const std::map &mapping) std::vector StretchCalculator::calculate(double ratio, size_t inputDuration, - const std::vector &phaseResetDf, - const std::vector &stretchDf) + const std::vector &phaseResetDf) { - assert(phaseResetDf.size() == stretchDf.size()); - m_peaks = findPeaks(phaseResetDf); size_t totalCount = phaseResetDf.size(); @@ -107,16 +104,6 @@ StretchCalculator::calculate(double ratio, size_t inputDuration, 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) { @@ -141,47 +128,61 @@ StretchCalculator::calculate(double ratio, size_t inputDuration, 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; - regionTotalChunks += regionDuration; - std::vector dfRegion; + size_t nchunks = regionEndChunk - regionStartChunk; - for (size_t j = regionStartChunk; j != regionEndChunk; ++j) { - dfRegion.push_back(stretchDf[j]); + if (nchunks == 0) { + //!!! + break; } - 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); - + double per = double(regionDuration) / double(nchunks); + double acc = 0.0; + size_t nremaining = nchunks; 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; - + 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 (totalForRegion != regionDuration) { - std::cerr << "*** ERROR: distributeRegion returned wrong duration " << totalForRegion << ", expected " << regionDuration << std::endl; + 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; @@ -189,7 +190,6 @@ StretchCalculator::calculate(double ratio, size_t inputDuration, 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; @@ -861,285 +861,5 @@ StretchCalculator::smoothDF(const std::vector &df) 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/StretchCalculator.h b/src/StretchCalculator.h index 416b0da..13334e1 100644 --- a/src/StretchCalculator.h +++ b/src/StretchCalculator.h @@ -51,13 +51,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 @@ -96,16 +93,6 @@ 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; diff --git a/src/StretcherImpl.cpp b/src/StretcherImpl.cpp index cb9d2f0..cfc16a8 100644 --- a/src/StretcherImpl.cpp +++ b/src/StretcherImpl.cpp @@ -25,9 +25,7 @@ #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" @@ -105,7 +103,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), @@ -150,12 +147,7 @@ RubberBandStretcher::Impl::Impl(size_t sampleRate, } if (m_options & OptionProcessRealTime) { - m_realtime = true; - - if (!(m_options & OptionStretchPrecise)) { - m_options |= OptionStretchPrecise; - } } #ifndef NO_THREADING @@ -203,7 +195,6 @@ RubberBandStretcher::Impl::~Impl() } delete m_phaseResetAudioCurve; - delete m_stretchAudioCurve; delete m_silentAudioCurve; delete m_stretchCalculator; delete m_studyFFT; @@ -249,7 +240,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; @@ -701,10 +691,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,17 +700,6 @@ 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, @@ -765,7 +740,6 @@ RubberBandStretcher::Impl::reconfigure() // the df vectors calculateStretch(); m_phaseResetDf.clear(); - m_stretchDf.clear(); m_silence.clear(); m_inputDuration = 0; } @@ -854,9 +828,6 @@ 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; } @@ -1069,9 +1040,6 @@ 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) { @@ -1163,28 +1131,10 @@ RubberBandStretcher::Impl::calculateStretch() } } -/* - 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) { diff --git a/src/StretcherImpl.h b/src/StretcherImpl.h index ceb5f12..b75fc9e 100644 --- a/src/StretcherImpl.h +++ b/src/StretcherImpl.h @@ -220,19 +220,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 +246,6 @@ protected: Scavenger > m_emergencyScavenger; CompoundAudioCurve *m_phaseResetAudioCurve; - AudioCurveCalculator *m_stretchAudioCurve; AudioCurveCalculator *m_silentAudioCurve; StretchCalculator *m_stretchCalculator; diff --git a/src/StretcherProcess.cpp b/src/StretcherProcess.cpp index edc18c9..78365b3 100644 --- a/src/StretcherProcess.cpp +++ b/src/StretcherProcess.cpp @@ -22,11 +22,6 @@ */ #include "StretcherImpl.h" - -#include "audiocurves/PercussiveAudioCurve.h" -#include "audiocurves/HighFrequencyAudioCurve.h" -#include "audiocurves/ConstantAudioCurve.h" - #include "StretchCalculator.h" #include "StretcherChannelData.h" @@ -213,12 +208,10 @@ RubberBandStretcher::Impl::consumeChannel(size_t c, 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 +228,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 } @@ -1102,12 +1093,11 @@ RubberBandStretcher::Impl::writeChunk(size_t channel, size_t shiftIncrement, boo 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 +1107,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, diff --git a/src/audiocurves/ConstantAudioCurve.cpp b/src/audiocurves/ConstantAudioCurve.cpp deleted file mode 100644 index 0c2e08a..0000000 --- a/src/audiocurves/ConstantAudioCurve.cpp +++ /dev/null @@ -1,57 +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 "ConstantAudioCurve.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; -} - -} - diff --git a/src/audiocurves/ConstantAudioCurve.h b/src/audiocurves/ConstantAudioCurve.h deleted file mode 100644 index b1a8e50..0000000 --- a/src/audiocurves/ConstantAudioCurve.h +++ /dev/null @@ -1,45 +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_CONSTANT_AUDIO_CURVE_H -#define RUBBERBAND_CONSTANT_AUDIO_CURVE_H - -#include "../dsp/AudioCurveCalculator.h" - -namespace RubberBand -{ - -class ConstantAudioCurve : public AudioCurveCalculator -{ -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(); -}; - -} - -#endif 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/audiocurves/SpectralDifferenceAudioCurve.h b/src/audiocurves/SpectralDifferenceAudioCurve.h deleted file mode 100644 index c78383d..0000000 --- a/src/audiocurves/SpectralDifferenceAudioCurve.h +++ /dev/null @@ -1,54 +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_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; -}; - -} - -#endif From d6aa3a59c21618b174188fa200816d91ac1013a2 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 18 May 2022 17:51:20 +0100 Subject: [PATCH 002/184] Begin some R3 work --- meson.build | 1 + src/R3StretcherImpl.h | 150 +++++++++++++++++++++++++++++++++++++ src/StretcherImpl.cpp | 8 +- src/StretcherImpl.h | 2 - src/dsp/BinClassifier.h | 159 ++++++++++++++++++++++++++++++++++++++++ src/dsp/MovingMedian.h | 3 + src/dsp/Window.h | 4 +- src/temporary.cpp | 1 + 8 files changed, 319 insertions(+), 9 deletions(-) create mode 100644 src/R3StretcherImpl.h create mode 100644 src/dsp/BinClassifier.h create mode 100644 src/temporary.cpp diff --git a/meson.build b/meson.build index 679b897..4a6dc38 100644 --- a/meson.build +++ b/meson.build @@ -48,6 +48,7 @@ library_sources = [ 'src/system/Thread.cpp', 'src/StretcherChannelData.cpp', 'src/StretcherImpl.cpp', + 'src/temporary.cpp', ] jni_sources = [ diff --git a/src/R3StretcherImpl.h b/src/R3StretcherImpl.h new file mode 100644 index 0000000..16d6e5d --- /dev/null +++ b/src/R3StretcherImpl.h @@ -0,0 +1,150 @@ +/* -*- 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 +#include + +#include "dsp/BinClassifier.h" +#include "dsp/FFT.h" + +namespace RubberBand +{ + +class R3StretcherImpl +{ +public: + R3StretcherImpl(int sampleRate, int channels); + ~R3StretcherImpl(); + + void reset(); + + void setTimeRatio(double ratio); + void setPitchScale(double scale); + + double getTimeRatio() const; + double getPitchScale() const; + +protected: + int m_sampleRate; + int m_channels; + + double m_timeRatio; + double m_pitchScale; + + struct FftBand { + int fftSize; + float f0; + float f1; + FftBand(int _s, float _f0, float _f1) : + fftSize(_s), f0(_f0), f1(_f1) { } + }; + + struct PhaseLockBand { + int p; + float beta; + float f0; + float f1; + PhaseLockBand(int _p, float _beta, float _f0, float _f1) : + p(_p), beta(_beta), f0(_f0), f1(_f1) { } + }; + + struct Range { + bool present; + float f0; + float f1; + Range() : present(false), f0(0.f), f1(0.f) { } + }; + + struct Guidance { + FftBand fftBands[3]; + PhaseLockBand phaseLockBands[5]; + Range kick; + Range lowPercussive; + Range phaseReset; + Range highPercussive; + Range channelLock; + }; + + struct BinSegmentation { + float percussiveBelow; + float percussiveAbove; + float residualAbove; + BinSegmentation(float _pb, float _pa, float _ra) : + percussiveBelow(_pb), percussiveAbove(_pa), residualAbove(_ra) { } + }; + + struct ChannelScaleData { + int fftSize; + int bufSize; // size of every array here: fftSize/2 + 1 + float *mag; + float *phase; + int *nearestPeaks; + int *nearestTroughs; + float *prevOutMag; + float *prevOutPhase; + int *prevNearestPeaks; + + ChannelScaleData(int _fftSize) : + fftSize(_fftSize), bufSize(_fftSize/2 + 1), + mag(allocate_and_zero(size_t(bufSize))), + phase(allocate_and_zero(size_t(bufSize))), + nearestPeaks(allocate_and_zero(size_t(bufSize))), + nearestTroughs(allocate_and_zero(size_t(bufSize))), + prevOutMag(allocate_and_zero(size_t(bufSize))), + prevOutPhase(allocate_and_zero(size_t(bufSize))), + prevNearestPeaks(allocate_and_zero(size_t(bufSize))) { } + + ~ChannelScaleData() { + deallocate(mag); + deallocate(phase); + deallocate(nearestPeaks); + deallocate(nearestTroughs); + deallocate(prevOutMag); + deallocate(prevOutPhase); + deallocate(prevNearestPeaks); + } + + private: + ChannelScaleData(const ChannelScaleData &) =delete; + ChannelScaleData &operator=(const ChannelScaleData &) =delete; + }; + + struct ChannelData { + std::map> scales; + std::unique_ptr classifier; + BinSegmentation segmentation; + BinSegmentation prevSegmentation; + BinSegmentation nextSegmentation; + Guidance guidance; + }; + + std::map> m_ffts; + +}; + +} + +#endif diff --git a/src/StretcherImpl.cpp b/src/StretcherImpl.cpp index cfc16a8..97c43c8 100644 --- a/src/StretcherImpl.cpp +++ b/src/StretcherImpl.cpp @@ -43,8 +43,6 @@ #include #include -using namespace RubberBand; - using std::cerr; using std::endl; using std::vector; @@ -619,7 +617,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); @@ -768,7 +766,7 @@ RubberBandStretcher::Impl::reconfigure() 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_windows[m_aWindowSize] = new Window - (HanningWindow, m_aWindowSize); + (HannWindow, m_aWindowSize); m_sincs[m_aWindowSize] = new SincWindow (m_aWindowSize, m_aWindowSize); } @@ -776,7 +774,7 @@ RubberBandStretcher::Impl::reconfigure() 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_windows[m_sWindowSize] = new Window - (HanningWindow, m_sWindowSize); + (HannWindow, m_sWindowSize); m_sincs[m_sWindowSize] = new SincWindow (m_sWindowSize, m_sWindowSize); } diff --git a/src/StretcherImpl.h b/src/StretcherImpl.h index b75fc9e..6485e6e 100644 --- a/src/StretcherImpl.h +++ b/src/StretcherImpl.h @@ -40,8 +40,6 @@ #include #include -using namespace RubberBand; - namespace RubberBand { diff --git a/src/dsp/BinClassifier.h b/src/dsp/BinClassifier.h new file mode 100644 index 0000000..348d0d7 --- /dev/null +++ b/src/dsp/BinClassifier.h @@ -0,0 +1,159 @@ +/* -*- 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 "../system/Allocators.h" +#include "../dsp/MovingMedian.h" +#include "../base/RingBuffer.h" + +#include +#include + +namespace RubberBand { + +class BinClassifier { + + enum class Classification { + Harmonic = 0, + Percussive = 1, + Residual = 2, + Silent = 3 + }; + + struct Parameters { + int binCount; + int horizontalFilterLength; + int horizontalFilterLag; + int verticalFilterLength; + double harmonicThreshold; + double percussiveThreshold; + float silenceThreshold; + Parameters(int _binCount, int _horizontalFilterLength, + int _horizontalFilterLag, int _verticalFilterLength, + double _harmonicThreshold, double _percussiveThreshold, + float _silenceThreshold) : + binCount(_binCount), + horizontalFilterLength(_horizontalFilterLength), + horizontalFilterLag(_horizontalFilterLag), + verticalFilterLength(_verticalFilterLength), + harmonicThreshold(_harmonicThreshold), + percussiveThreshold(_percussiveThreshold), + silenceThreshold(_silenceThreshold) { } + }; + + BinClassifier(Parameters parameters) : + m_parameters(parameters), + m_vfQueue(parameters.horizontalFilterLag) + { + int n = m_parameters.binCount; + + for (int i = 0; i < n; ++i) { + m_hFilters.push_back(std::make_shared> + (m_parameters.horizontalFilterLength)); + } + + m_vFilter = std::make_unique> + (m_parameters.verticalFilterLength); + + m_hf = allocate_and_zero(n); + m_vf = allocate_and_zero(n); + + for (int i = 0; i < m_parameters.horizontalFilterLag; ++i) { + float *entry = allocate_and_zero(n); + m_vfQueue.write(&entry, 1); + } + } + + ~BinClassifier() + { + while (m_vfQueue.getReadSpace() > 0) { + float *entry = m_vfQueue.readOne(); + deallocate(entry); + } + + deallocate(m_hf); + deallocate(m_vf); + } + + void classify(const float *const mag, Classification *classification) { + const int n = m_parameters.binCount; + + for (int i = 0; i < n; ++i) { + m_hFilters[i]->push(mag[i]); + m_hf[i] = m_hFilters[i]->get(); + } + + m_vFilter->reset(); + int vFilterLag = m_parameters.verticalFilterLength / 2; + + for (int i = 0; i < vFilterLag; ++i) { + m_vFilter->push(mag[i]); + } + for (int i = vFilterLag; i < n; ++i) { + m_vFilter->push(mag[i]); + m_vf[i-vFilterLag] = m_vFilter->get(); + } + for (int i = n; i < n + vFilterLag; ++i) { + m_vFilter->push(0.f); + m_vf[i-vFilterLag] = m_vFilter->get(); + } + + if (m_parameters.horizontalFilterLag > 0) { + float *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 (mag[i] < m_parameters.silenceThreshold) { + c = Classification::Silent; + } else 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::vector>> m_hFilters; + std::unique_ptr> m_vFilter; + float *m_hf; + float *m_vf; + RingBuffer m_vfQueue; +}; + +} + +#endif diff --git a/src/dsp/MovingMedian.h b/src/dsp/MovingMedian.h index ba18390..4d6389c 100644 --- a/src/dsp/MovingMedian.h +++ b/src/dsp/MovingMedian.h @@ -102,6 +102,9 @@ private: v_move(index, index + 1, m_sortend - index); *m_sortend = T(0); } + + MovingMedian(const MovingMedian &) =delete; + MovingMedian &operator=(const MovingMedian &) =delete; }; } diff --git a/src/dsp/Window.h b/src/dsp/Window.h index c6c9b81..bfd947a 100644 --- a/src/dsp/Window.h +++ b/src/dsp/Window.h @@ -38,7 +38,7 @@ enum WindowType { RectangularWindow, BartlettWindow, HammingWindow, - HanningWindow, + HannWindow, BlackmanWindow, GaussianWindow, ParzenWindow, @@ -136,7 +136,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; diff --git a/src/temporary.cpp b/src/temporary.cpp new file mode 100644 index 0000000..75cf924 --- /dev/null +++ b/src/temporary.cpp @@ -0,0 +1 @@ +#include "R3StretcherImpl.h" From 4241c0f6a479f51222c6959d8564220b7b462c1b Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 19 May 2022 09:16:13 +0100 Subject: [PATCH 003/184] Add bin segmenter --- src/R3StretcherImpl.h | 19 ++---- src/dsp/BinClassifier.h | 26 +++----- src/dsp/BinSegmenter.h | 129 ++++++++++++++++++++++++++++++++++++++++ src/dsp/MovingMedian.h | 21 +++++++ 4 files changed, 165 insertions(+), 30 deletions(-) create mode 100644 src/dsp/BinSegmenter.h diff --git a/src/R3StretcherImpl.h b/src/R3StretcherImpl.h index 16d6e5d..420a03f 100644 --- a/src/R3StretcherImpl.h +++ b/src/R3StretcherImpl.h @@ -27,8 +27,9 @@ #include #include -#include "dsp/BinClassifier.h" +#include "dsp/BinSegmenter.h" #include "dsp/FFT.h" +#include "system/Allocators.h" namespace RubberBand { @@ -88,14 +89,6 @@ protected: Range channelLock; }; - struct BinSegmentation { - float percussiveBelow; - float percussiveAbove; - float residualAbove; - BinSegmentation(float _pb, float _pa, float _ra) : - percussiveBelow(_pb), percussiveAbove(_pa), residualAbove(_ra) { } - }; - struct ChannelScaleData { int fftSize; int bufSize; // size of every array here: fftSize/2 + 1 @@ -134,10 +127,10 @@ protected: struct ChannelData { std::map> scales; - std::unique_ptr classifier; - BinSegmentation segmentation; - BinSegmentation prevSegmentation; - BinSegmentation nextSegmentation; + std::unique_ptr segmenter; + BinSegmenter::Segmentation segmentation; + BinSegmenter::Segmentation prevSegmentation; + BinSegmenter::Segmentation nextSegmentation; Guidance guidance; }; diff --git a/src/dsp/BinClassifier.h b/src/dsp/BinClassifier.h index 348d0d7..59e17fd 100644 --- a/src/dsp/BinClassifier.h +++ b/src/dsp/BinClassifier.h @@ -33,8 +33,9 @@ namespace RubberBand { -class BinClassifier { - +class BinClassifier +{ +public: enum class Classification { Harmonic = 0, Percussive = 1, @@ -104,21 +105,9 @@ class BinClassifier { m_hFilters[i]->push(mag[i]); m_hf[i] = m_hFilters[i]->get(); } - - m_vFilter->reset(); - int vFilterLag = m_parameters.verticalFilterLength / 2; - - for (int i = 0; i < vFilterLag; ++i) { - m_vFilter->push(mag[i]); - } - for (int i = vFilterLag; i < n; ++i) { - m_vFilter->push(mag[i]); - m_vf[i-vFilterLag] = m_vFilter->get(); - } - for (int i = n; i < n + vFilterLag; ++i) { - m_vFilter->push(0.f); - m_vf[i-vFilterLag] = m_vFilter->get(); - } + + v_copy(m_vf, mag, n); + MovingMedian::filter(*m_vFilter, m_vf); if (m_parameters.horizontalFilterLag > 0) { float *lagged = m_vfQueue.readOne(); @@ -152,6 +141,9 @@ protected: float *m_hf; float *m_vf; RingBuffer m_vfQueue; + + BinClassifier(const BinClassifier &) =delete; + BinClassifier &operator=(const BinClassifier &) =delete; }; } diff --git a/src/dsp/BinSegmenter.h b/src/dsp/BinSegmenter.h new file mode 100644 index 0000000..dd3b4ea --- /dev/null +++ b/src/dsp/BinSegmenter.h @@ -0,0 +1,129 @@ +/* -*- 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 + +namespace RubberBand { + +class BinSegmenter +{ +public: + struct Segmentation { + double percussiveBelow; + double percussiveAbove; + double residualAbove; + Segmentation(double _pb, double _pa, double _ra) : + percussiveBelow(_pb), percussiveAbove(_pa), residualAbove(_ra) { } + }; + + struct Parameters { + int fftSize; + double sampleRate; + Parameters(int _fftSize, double _sampleRate) : + fftSize(_fftSize), sampleRate(_sampleRate) { } + }; + + BinSegmenter(Parameters parameters, + BinClassifier::Parameters classifierParameters) : + m_parameters(parameters), + m_classifierParameters(classifierParameters), + m_classifier(classifierParameters), + m_classification(classifierParameters.binCount, + BinClassifier::Classification::Silent), + m_numeric(classifierParameters.binCount, 0), + m_classFilter(classifierParameters.binCount / 64) + { + } + + Segmentation segment(const float *const mag) { + int n = m_classifierParameters.binCount; + m_classifier.classify(mag, m_classification.data()); + for (int i = 0; i < n; ++i) { + switch (m_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; + } + } + MovingMedian::filter(m_classFilter, m_numeric.data()); + double f0 = 0.0; + for (int i = 1; i < n; ++i) { + if (m_numeric[i] != 1) { + f0 = frequencyForBin(i); + break; + } + } + double nyquist = m_parameters.sampleRate / 2.0; + int top = binForFrequency(16000.0); + if (top >= n) top = n-1; + double f1 = nyquist; + double f2 = nyquist; + bool inPercussive = false; + for (int i = top; i > 0; --i) { + if (m_numeric[i] == 1) { // percussive + if (!inPercussive) { + inPercussive = true; + f2 = frequencyForBin(i); + continue; + } + } else if (m_numeric[i] == 0) { // harmonic + if (inPercussive) { + f1 = frequencyForBin(i); + } + break; // always when harmonic reached + } + } + return Segmentation(f0, f1, f2); + } + +protected: + Parameters m_parameters; + BinClassifier::Parameters m_classifierParameters; + BinClassifier m_classifier; + std::vector m_classification; + std::vector m_numeric; + MovingMedian m_classFilter; + + int binForFrequency(double f) { + return int(round(f * double(m_parameters.fftSize) / + m_parameters.sampleRate)); + } + double frequencyForBin(int b) { + return (double(b) * m_parameters.sampleRate) + / double(m_parameters.fftSize); + } + + BinSegmenter(const BinSegmenter &) =delete; + BinSegmenter &operator=(const BinSegmenter &) =delete; +}; + +} + +#endif diff --git a/src/dsp/MovingMedian.h b/src/dsp/MovingMedian.h index 4d6389c..12b144d 100644 --- a/src/dsp/MovingMedian.h +++ b/src/dsp/MovingMedian.h @@ -80,6 +80,27 @@ public: v_zero(m_sorted, P::m_size); } + // Convenience function that applies a given filter to an array + // in-place. Array must have length equal to getSize(). Modifies + // both the filter and the array. + // + static void filter(MovingMedian &mm, T *v) { + int n = mm.getSize(); + int lag = n / 2; + mm.reset(); + for (int i = 0; i < lag; ++i) { + mm.push(v[i]); + } + for (int i = lag; i < n; ++i) { + mm.push(v[i]); + v[i-lag] = mm.get(); + } + for (int i = n; i < n + lag; ++i) { + mm.push(T()); + v[i-lag] = mm.get(); + } + } + private: T *const m_frame; T *const m_sorted; From e9264ae90996488ecb4818d73d80ee524e83076f Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 19 May 2022 13:13:47 +0100 Subject: [PATCH 004/184] Add peak finder --- src/R3StretcherImpl.h | 2 + src/dsp/Peak.h | 127 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 src/dsp/Peak.h diff --git a/src/R3StretcherImpl.h b/src/R3StretcherImpl.h index 420a03f..075808c 100644 --- a/src/R3StretcherImpl.h +++ b/src/R3StretcherImpl.h @@ -31,6 +31,8 @@ #include "dsp/FFT.h" #include "system/Allocators.h" +#include "dsp/Peak.h" + namespace RubberBand { diff --git a/src/dsp/Peak.h b/src/dsp/Peak.h new file mode 100644 index 0000000..f73ab71 --- /dev/null +++ b/src/dsp/Peak.h @@ -0,0 +1,127 @@ +/* -*- 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(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 *const 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 *const v, + int rangeStart, + int rangeCount, + int p, + int *nearest, + int *next = nullptr) + { + int nPeaks = 0; + int n = rangeStart + rangeCount; + + 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 && x <= v[k]) { + good = false; + break; + } + if (k > i && x < v[k]) { + 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]; + } + if (next) { + if (pp == i) { + 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 From e9ad04e2b48bf09fe4a91c666d263ec9c97cc8d9 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 19 May 2022 13:34:51 +0100 Subject: [PATCH 005/184] Reorganise into faster (R2) and finer (R3) --- ladspa-lv2/RubberBandPitchShifter.h | 2 +- main/main.cpp | 7 ++-- meson.build | 32 +++++++++---------- src/RubberBandStretcher.cpp | 2 +- src/{system => common}/Allocators.cpp | 0 src/{system => common}/Allocators.h | 0 src/{dsp => common}/BQResampler.cpp | 4 +-- src/{dsp => common}/BQResampler.h | 4 +-- src/{dsp => common}/FFT.cpp | 10 +++--- src/{dsp => common}/FFT.h | 2 +- src/{dsp => common}/MovingMedian.h | 4 +-- src/{base => common}/Profiler.cpp | 2 +- src/{base => common}/Profiler.h | 2 +- src/{dsp => common}/Resampler.cpp | 4 +-- src/{dsp => common}/Resampler.h | 2 +- src/{base => common}/RingBuffer.h | 4 +-- src/{dsp => common}/SampleFilter.h | 0 src/{base => common}/Scavenger.h | 6 ++-- src/{system => common}/Thread.cpp | 0 src/{system => common}/Thread.h | 0 src/{system => common}/VectorOps.h | 0 src/{system => common}/VectorOpsComplex.cpp | 0 src/{system => common}/VectorOpsComplex.h | 2 -- src/{dsp => common}/Window.h | 6 ++-- src/{system => common}/sysutils.cpp | 26 --------------- src/{system => common}/sysutils.h | 3 -- src/{ => ext}/float_cast/float_cast.h | 0 src/{ => ext}/getopt/getopt.c | 0 src/{ => ext}/getopt/getopt.h | 0 src/{ => ext}/getopt/getopt_long.c | 0 src/{ => ext}/kissfft/COPYING | 0 src/{ => ext}/kissfft/_kiss_fft_guts.h | 0 src/{ => ext}/kissfft/kiss_fft.c | 0 src/{ => ext}/kissfft/kiss_fft.h | 0 src/{ => ext}/kissfft/kiss_fft_log.h | 0 src/{ => ext}/kissfft/kiss_fftr.c | 0 src/{ => ext}/kissfft/kiss_fftr.h | 0 src/{ => ext}/pommier/neon_mathfun.h | 0 src/{ => ext}/pommier/sse_mathfun.h | 0 src/{ => ext}/speex/COPYING | 0 src/{ => ext}/speex/resample.c | 0 src/{ => ext}/speex/speex_resampler.h | 0 src/{dsp => faster}/AudioCurveCalculator.cpp | 0 src/{dsp => faster}/AudioCurveCalculator.h | 2 +- .../CompoundAudioCurve.cpp | 2 +- .../CompoundAudioCurve.h | 2 +- .../HighFrequencyAudioCurve.cpp | 0 .../HighFrequencyAudioCurve.h | 2 +- .../PercussiveAudioCurve.cpp | 4 +-- .../PercussiveAudioCurve.h | 2 +- .../SilentAudioCurve.cpp | 0 .../SilentAudioCurve.h | 2 +- src/{dsp => faster}/SincWindow.h | 6 ++-- src/{ => faster}/StretchCalculator.cpp | 2 +- src/{ => faster}/StretchCalculator.h | 0 src/{ => faster}/StretcherChannelData.cpp | 5 ++- src/{ => faster}/StretcherChannelData.h | 0 src/{ => faster}/StretcherImpl.cpp | 17 ++++------ src/{ => faster}/StretcherImpl.h | 17 +++++----- src/{ => faster}/StretcherProcess.cpp | 8 ++--- src/{dsp => finer}/BinClassifier.h | 6 ++-- src/{dsp => finer}/BinSegmenter.h | 1 + src/{dsp => finer}/Peak.h | 0 src/{ => finer}/R3StretcherImpl.h | 8 ++--- src/temporary.cpp | 2 +- vamp/RubberBandVampPlugin.cpp | 4 +-- 66 files changed, 89 insertions(+), 127 deletions(-) rename src/{system => common}/Allocators.cpp (100%) rename src/{system => common}/Allocators.h (100%) rename src/{dsp => common}/BQResampler.cpp (99%) rename src/{dsp => common}/BQResampler.h (98%) rename src/{dsp => common}/FFT.cpp (99%) rename src/{dsp => common}/FFT.h (99%) rename src/{dsp => common}/MovingMedian.h (99%) rename src/{base => common}/Profiler.cpp (99%) rename src/{base => common}/Profiler.h (98%) rename src/{dsp => common}/Resampler.cpp (99%) rename src/{dsp => common}/Resampler.h (99%) rename src/{base => common}/RingBuffer.h (99%) rename src/{dsp => common}/SampleFilter.h (100%) rename src/{base => common}/Scavenger.h (98%) rename src/{system => common}/Thread.cpp (100%) rename src/{system => common}/Thread.h (100%) rename src/{system => common}/VectorOps.h (100%) rename src/{system => common}/VectorOpsComplex.cpp (100%) rename src/{system => common}/VectorOpsComplex.h (98%) rename src/{dsp => common}/Window.h (97%) rename src/{system => common}/sysutils.cpp (90%) rename src/{system => common}/sysutils.h (96%) rename src/{ => ext}/float_cast/float_cast.h (100%) rename src/{ => ext}/getopt/getopt.c (100%) rename src/{ => ext}/getopt/getopt.h (100%) rename src/{ => ext}/getopt/getopt_long.c (100%) rename src/{ => ext}/kissfft/COPYING (100%) rename src/{ => ext}/kissfft/_kiss_fft_guts.h (100%) rename src/{ => ext}/kissfft/kiss_fft.c (100%) rename src/{ => ext}/kissfft/kiss_fft.h (100%) rename src/{ => ext}/kissfft/kiss_fft_log.h (100%) rename src/{ => ext}/kissfft/kiss_fftr.c (100%) rename src/{ => ext}/kissfft/kiss_fftr.h (100%) rename src/{ => ext}/pommier/neon_mathfun.h (100%) rename src/{ => ext}/pommier/sse_mathfun.h (100%) rename src/{ => ext}/speex/COPYING (100%) rename src/{ => ext}/speex/resample.c (100%) rename src/{ => ext}/speex/speex_resampler.h (100%) rename src/{dsp => faster}/AudioCurveCalculator.cpp (100%) rename src/{dsp => faster}/AudioCurveCalculator.h (99%) rename src/{audiocurves => faster}/CompoundAudioCurve.cpp (99%) rename src/{audiocurves => faster}/CompoundAudioCurve.h (98%) rename src/{audiocurves => faster}/HighFrequencyAudioCurve.cpp (100%) rename src/{audiocurves => faster}/HighFrequencyAudioCurve.h (97%) rename src/{audiocurves => faster}/PercussiveAudioCurve.cpp (97%) rename src/{audiocurves => faster}/PercussiveAudioCurve.h (97%) rename src/{audiocurves => faster}/SilentAudioCurve.cpp (100%) rename src/{audiocurves => faster}/SilentAudioCurve.h (97%) rename src/{dsp => faster}/SincWindow.h (97%) rename src/{ => faster}/StretchCalculator.cpp (99%) rename src/{ => faster}/StretchCalculator.h (100%) rename src/{ => faster}/StretcherChannelData.cpp (99%) rename src/{ => faster}/StretcherChannelData.h (100%) rename src/{ => faster}/StretcherImpl.cpp (99%) rename src/{ => faster}/StretcherImpl.h (96%) rename src/{ => faster}/StretcherProcess.cpp (99%) rename src/{dsp => finer}/BinClassifier.h (97%) rename src/{dsp => finer}/BinSegmenter.h (99%) rename src/{dsp => finer}/Peak.h (100%) rename src/{ => finer}/R3StretcherImpl.h (97%) 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/main/main.cpp b/main/main.cpp index 90f75dc..25e66ac 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -33,18 +33,17 @@ #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 diff --git a/meson.build b/meson.build index 4a6dc38..0a1dfae 100644 --- a/meson.build +++ b/meson.build @@ -33,21 +33,21 @@ 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/HighFrequencyAudioCurve.cpp', - 'src/audiocurves/SilentAudioCurve.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/StretchCalculator.cpp', + 'src/faster/StretcherChannelData.cpp', + 'src/faster/StretcherImpl.cpp', + 'src/faster/StretcherProcess.cpp', + 'src/common/Profiler.cpp', + 'src/common/Resampler.cpp', + 'src/common/FFT.cpp', + 'src/common/Allocators.cpp', + 'src/common/sysutils.cpp', + 'src/common/Thread.cpp', 'src/temporary.cpp', ] @@ -203,7 +203,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' diff --git a/src/RubberBandStretcher.cpp b/src/RubberBandStretcher.cpp index 394cc03..5791a94 100644 --- a/src/RubberBandStretcher.cpp +++ b/src/RubberBandStretcher.cpp @@ -21,7 +21,7 @@ you must obtain a valid commercial licence before doing so. */ -#include "StretcherImpl.h" +#include "faster/StretcherImpl.h" namespace RubberBand { 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/dsp/MovingMedian.h b/src/common/MovingMedian.h similarity index 99% rename from src/dsp/MovingMedian.h rename to src/common/MovingMedian.h index 12b144d..c12b18e 100644 --- a/src/dsp/MovingMedian.h +++ b/src/common/MovingMedian.h @@ -25,11 +25,9 @@ #define RUBBERBAND_MOVING_MEDIAN_H #include "SampleFilter.h" - -#include "../system/Allocators.h" +#include "Allocators.h" #include - #include namespace RubberBand 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..83ba0df 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 diff --git a/src/dsp/Resampler.h b/src/common/Resampler.h similarity index 99% rename from src/dsp/Resampler.h rename to src/common/Resampler.h index 94abef3..142ce58 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 { diff --git a/src/base/RingBuffer.h b/src/common/RingBuffer.h similarity index 99% rename from src/base/RingBuffer.h rename to src/common/RingBuffer.h index 9bb91e0..fb80788 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 diff --git a/src/dsp/SampleFilter.h b/src/common/SampleFilter.h similarity index 100% rename from src/dsp/SampleFilter.h rename to src/common/SampleFilter.h 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/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 97% rename from src/dsp/Window.h rename to src/common/Window.h index bfd947a..e4d6a05 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 { diff --git a/src/system/sysutils.cpp b/src/common/sysutils.cpp similarity index 90% rename from src/system/sysutils.cpp rename to src/common/sysutils.cpp index 8ae09d8..819767a 100644 --- a/src/system/sysutils.cpp +++ b/src/common/sysutils.cpp @@ -28,7 +28,6 @@ #include #include #else /* !_WIN32 */ -#include #include #ifdef __APPLE__ #include @@ -209,31 +208,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 96% rename from src/system/sysutils.h rename to src/common/sysutils.h index a6271cd..adf9aba 100644 --- a/src/system/sysutils.h +++ b/src/common/sysutils.h @@ -87,9 +87,6 @@ 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); 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/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/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/StretchCalculator.cpp b/src/faster/StretchCalculator.cpp similarity index 99% rename from src/StretchCalculator.cpp rename to src/faster/StretchCalculator.cpp index da228a3..87580e8 100644 --- a/src/StretchCalculator.cpp +++ b/src/faster/StretchCalculator.cpp @@ -30,7 +30,7 @@ #include #include -#include "system/sysutils.h" +#include "../common/sysutils.h" namespace RubberBand { diff --git a/src/StretchCalculator.h b/src/faster/StretchCalculator.h similarity index 100% rename from src/StretchCalculator.h rename to src/faster/StretchCalculator.h diff --git a/src/StretcherChannelData.cpp b/src/faster/StretcherChannelData.cpp similarity index 99% rename from src/StretcherChannelData.cpp rename to src/faster/StretcherChannelData.cpp index 1c555dc..3d379c6 100644 --- a/src/StretcherChannelData.cpp +++ b/src/faster/StretcherChannelData.cpp @@ -23,9 +23,8 @@ #include "StretcherChannelData.h" -#include "dsp/Resampler.h" - -#include "system/Allocators.h" +#include "../common/Resampler.h" +#include "../common/Allocators.h" #include diff --git a/src/StretcherChannelData.h b/src/faster/StretcherChannelData.h similarity index 100% rename from src/StretcherChannelData.h rename to src/faster/StretcherChannelData.h diff --git a/src/StretcherImpl.cpp b/src/faster/StretcherImpl.cpp similarity index 99% rename from src/StretcherImpl.cpp rename to src/faster/StretcherImpl.cpp index 97c43c8..a23ebbc 100644 --- a/src/StretcherImpl.cpp +++ b/src/faster/StretcherImpl.cpp @@ -23,19 +23,16 @@ #include "StretcherImpl.h" -#include "audiocurves/PercussiveAudioCurve.h" -#include "audiocurves/HighFrequencyAudioCurve.h" -#include "audiocurves/SilentAudioCurve.h" -#include "audiocurves/CompoundAudioCurve.h" - -#include "dsp/Resampler.h" - +#include "PercussiveAudioCurve.h" +#include "HighFrequencyAudioCurve.h" +#include "SilentAudioCurve.h" +#include "CompoundAudioCurve.h" #include "StretchCalculator.h" #include "StretcherChannelData.h" -#include "base/Profiler.h" - -#include "system/sysutils.h" +#include "../common/Resampler.h" +#include "../common/Profiler.h" +#include "../common/sysutils.h" #include #include diff --git a/src/StretcherImpl.h b/src/faster/StretcherImpl.h similarity index 96% rename from src/StretcherImpl.h rename to src/faster/StretcherImpl.h index 6485e6e..bcd0abd 100644 --- a/src/StretcherImpl.h +++ b/src/faster/StretcherImpl.h @@ -26,16 +26,15 @@ #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/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 diff --git a/src/StretcherProcess.cpp b/src/faster/StretcherProcess.cpp similarity index 99% rename from src/StretcherProcess.cpp rename to src/faster/StretcherProcess.cpp index 78365b3..cb79b2a 100644 --- a/src/StretcherProcess.cpp +++ b/src/faster/StretcherProcess.cpp @@ -25,10 +25,10 @@ #include "StretchCalculator.h" #include "StretcherChannelData.h" -#include "dsp/Resampler.h" -#include "base/Profiler.h" -#include "system/VectorOps.h" -#include "system/sysutils.h" +#include "../common/Resampler.h" +#include "../common/Profiler.h" +#include "../common/VectorOps.h" +#include "../common/sysutils.h" #include #include diff --git a/src/dsp/BinClassifier.h b/src/finer/BinClassifier.h similarity index 97% rename from src/dsp/BinClassifier.h rename to src/finer/BinClassifier.h index 59e17fd..a92b009 100644 --- a/src/dsp/BinClassifier.h +++ b/src/finer/BinClassifier.h @@ -24,9 +24,9 @@ #ifndef RUBBERBAND_BIN_CLASSIFIER_H #define RUBBERBAND_BIN_CLASSIFIER_H -#include "../system/Allocators.h" -#include "../dsp/MovingMedian.h" -#include "../base/RingBuffer.h" +#include "../common/Allocators.h" +#include "../common/MovingMedian.h" +#include "../common/RingBuffer.h" #include #include diff --git a/src/dsp/BinSegmenter.h b/src/finer/BinSegmenter.h similarity index 99% rename from src/dsp/BinSegmenter.h rename to src/finer/BinSegmenter.h index dd3b4ea..8bb78d4 100644 --- a/src/dsp/BinSegmenter.h +++ b/src/finer/BinSegmenter.h @@ -25,6 +25,7 @@ #define RUBBERBAND_BIN_SEGMENTER_H #include "BinClassifier.h" + #include namespace RubberBand { diff --git a/src/dsp/Peak.h b/src/finer/Peak.h similarity index 100% rename from src/dsp/Peak.h rename to src/finer/Peak.h diff --git a/src/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h similarity index 97% rename from src/R3StretcherImpl.h rename to src/finer/R3StretcherImpl.h index 075808c..8eb21b5 100644 --- a/src/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -27,11 +27,11 @@ #include #include -#include "dsp/BinSegmenter.h" -#include "dsp/FFT.h" -#include "system/Allocators.h" +#include "BinSegmenter.h" +#include "Peak.h" -#include "dsp/Peak.h" +#include "../common/FFT.h" +#include "../common/Allocators.h" namespace RubberBand { diff --git a/src/temporary.cpp b/src/temporary.cpp index 75cf924..39d8493 100644 --- a/src/temporary.cpp +++ b/src/temporary.cpp @@ -1 +1 @@ -#include "R3StretcherImpl.h" +#include "finer/R3StretcherImpl.h" diff --git a/vamp/RubberBandVampPlugin.cpp b/vamp/RubberBandVampPlugin.cpp index 00f2840..a3dbd6c 100644 --- a/vamp/RubberBandVampPlugin.cpp +++ b/vamp/RubberBandVampPlugin.cpp @@ -23,8 +23,8 @@ #include "RubberBandVampPlugin.h" -#include "StretchCalculator.h" -#include "system/sysutils.h" +#include "faster/StretchCalculator.h" +#include "common/sysutils.h" #include #include From 5584ccc059b79a66e705f32783e75735be90c4be Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 19 May 2022 15:25:33 +0100 Subject: [PATCH 006/184] Make comparator configurable (for troughs) --- src/finer/Peak.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/finer/Peak.h b/src/finer/Peak.h index f73ab71..197d31e 100644 --- a/src/finer/Peak.h +++ b/src/finer/Peak.h @@ -29,7 +29,7 @@ namespace RubberBand { -template +template > class Peak { public: @@ -64,6 +64,7 @@ public: { int nPeaks = 0; int n = rangeStart + rangeCount; + GreaterThan greater; for (int i = rangeStart; i < n; ++i) { T x = v[i]; @@ -71,11 +72,11 @@ public: for (int k = i - p; k <= i + p; ++k) { if (k < rangeStart || k == i) continue; if (k >= n) break; - if (k < i && x <= v[k]) { + if (k < i && !greater(x, v[k])) { good = false; break; } - if (k > i && x < v[k]) { + if (k > i && greater(v[k], x)) { good = false; break; } From cf602218f5b99ebc985c361014e166b9fb05aeae Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 19 May 2022 16:31:21 +0100 Subject: [PATCH 007/184] Add Guide --- src/common/sysutils.cpp | 5 - src/common/sysutils.h | 14 --- src/finer/BinSegmenter.h | 4 +- src/finer/Guide.h | 220 ++++++++++++++++++++++++++++++++++++ src/finer/Peak.h | 4 +- src/finer/R3StretcherImpl.h | 41 +------ 6 files changed, 228 insertions(+), 60 deletions(-) create mode 100644 src/finer/Guide.h diff --git a/src/common/sysutils.cpp b/src/common/sysutils.cpp index 819767a..6f36ae0 100644 --- a/src/common/sysutils.cpp +++ b/src/common/sysutils.cpp @@ -164,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() diff --git a/src/common/sysutils.h b/src/common/sysutils.h index adf9aba..798c5d6 100644 --- a/src/common/sysutils.h +++ b/src/common/sysutils.h @@ -92,10 +92,6 @@ 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))); } @@ -121,11 +117,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 @@ -154,11 +145,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/finer/BinSegmenter.h b/src/finer/BinSegmenter.h index 8bb78d4..6ae202c 100644 --- a/src/finer/BinSegmenter.h +++ b/src/finer/BinSegmenter.h @@ -112,11 +112,11 @@ protected: std::vector m_numeric; MovingMedian m_classFilter; - int binForFrequency(double f) { + int binForFrequency(double f) const { return int(round(f * double(m_parameters.fftSize) / m_parameters.sampleRate)); } - double frequencyForBin(int b) { + double frequencyForBin(int b) const { return (double(b) * m_parameters.sampleRate) / double(m_parameters.fftSize); } diff --git a/src/finer/Guide.h b/src/finer/Guide.h new file mode 100644 index 0000000..9541970 --- /dev/null +++ b/src/finer/Guide.h @@ -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 RUBBERBAND_GUIDE_H +#define RUBBERBAND_GUIDE_H + +namespace RubberBand +{ + +class Guide +{ +public: + struct FftBand { + int fftSize; + float f0; + float f1; + FftBand(int _s, float _f0, float _f1) : + fftSize(_s), f0(_f0), f1(_f1) { } + }; + + struct PhaseLockBand { + int p; + float beta; + float f0; + float f1; + PhaseLockBand(int _p, float _beta, float _f0, float _f1) : + p(_p), beta(_beta), f0(_f0), f1(_f1) { } + }; + + struct Range { + bool present; + float f0; + float f1; + Range() : present(false), f0(0.f), f1(0.f) { } + }; + + struct Guidance { + FftBand fftBands[3]; + PhaseLockBand phaseLockBands[5]; + Range kick; + Range lowPercussive; + Range highPercussive; + Range phaseReset; + Range channelLock; + }; + + struct Parameters { + int fftSize; + double sampleRate; + Parameters(int _fftSize, double _sampleRate) : + fftSize(_fftSize), sampleRate(_sampleRate) { } + }; + + Guide(Parameters parameters) : + m_parameters(parameters) { } + + void calculate(double ratio, + const float *const magnitudes, + const int *const troughs, + const float *const prevMagnitudes, + const BinSegmenter::Segmentation &segmentation, + const BinSegmenter::Segmentation &prevSegmentation, + const BinSegmenter::Segmentation &nextSegmentation, + Guidance &guidance) const { + + bool potentialKick = checkPotentialKick(magnitudes, prevMagnitudes); + + guidance.kick.present = false; + guidance.lowPercussive.present = false; + guidance.highPercussive.present = false; + guidance.phaseReset.present = false; + + guidance.channelLock.present = true; + guidance.channelLock.f0 = 0.0; + guidance.channelLock.f1 = 600.0; + + if (segmentation.percussiveBelow > 40.0) { + guidance.lowPercussive.present = true; + guidance.lowPercussive.f0 = 0.0; + guidance.lowPercussive.f1 = segmentation.percussiveBelow; + } + + if (potentialKick && prevSegmentation.percussiveBelow < 40.0) { + guidance.kick = guidance.lowPercussive; + } + + if (segmentation.residualAbove > segmentation.percussiveAbove) { + guidance.highPercussive.present = true; + guidance.highPercussive.f0 = segmentation.percussiveAbove; + guidance.highPercussive.f1 = segmentation.residualAbove; + } + + double bigGap = 4000.0; + if (ratio > 1.0 && + segmentation.residualAbove > + segmentation.percussiveAbove + bigGap && + prevSegmentation.residualAbove < + prevSegmentation.percussiveAbove + bigGap) { + guidance.phaseReset.present = true; + guidance.phaseReset.f0 = std::min(segmentation.percussiveAbove, + nextSegmentation.percussiveAbove); + if (guidance.phaseReset.f0 < 200.0) { + guidance.phaseReset.f0 = 0.0; + } + guidance.phaseReset.f1 = std::max(segmentation.residualAbove, + nextSegmentation.residualAbove); + } + + double higher = snapToTrough(4800.0, troughs); + double lower = snapToTrough(700.0, troughs); + double nyquist = m_parameters.sampleRate / 2.0; + + guidance.fftBands[0].fftSize = roundUp(int(ceil(nyquist/8.0))); + guidance.fftBands[0].f0 = 0.0; + guidance.fftBands[0].f1 = lower; + + guidance.fftBands[1].fftSize = roundUp(int(ceil(nyquist/16.0))); + guidance.fftBands[1].f0 = lower; + guidance.fftBands[1].f1 = higher; + + guidance.fftBands[2].fftSize = roundUp(int(ceil(nyquist/32.0))); + guidance.fftBands[2].f0 = higher; + guidance.fftBands[2].f1 = 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; + } + +protected: + Parameters m_parameters; + + int binForFrequency(double f) const { + return int(round(f * double(m_parameters.fftSize) / + m_parameters.sampleRate)); + } + double frequencyForBin(int b) const { + return (double(b) * m_parameters.sampleRate) + / double(m_parameters.fftSize); + } + + // 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; + } + + bool checkPotentialKick(const float *const magnitudes, + const float *const prevMagnitudes) const { + int b = binForFrequency(200.0); + float 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-3f && here > there * 1.4f); + } + + double snapToTrough(double f, const int *const troughs) const { + return frequencyForBin(troughs[binForFrequency(f)]); + } + + 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 index 197d31e..7be4bd2 100644 --- a/src/finer/Peak.h +++ b/src/finer/Peak.h @@ -42,7 +42,7 @@ public: // 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 *const v, + void findNearestAndNextPeaks(const T *v, int p, int *nearest, int *next = nullptr) @@ -55,7 +55,7 @@ public: // 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 *const v, + void findNearestAndNextPeaks(const T *v, int rangeStart, int rangeCount, int p, diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 8eb21b5..64c4665 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -28,6 +28,7 @@ #include #include "BinSegmenter.h" +#include "Guide.h" #include "Peak.h" #include "../common/FFT.h" @@ -39,7 +40,7 @@ namespace RubberBand class R3StretcherImpl { public: - R3StretcherImpl(int sampleRate, int channels); + R3StretcherImpl(double sampleRate, int channels); ~R3StretcherImpl(); void reset(); @@ -51,46 +52,12 @@ public: double getPitchScale() const; protected: - int m_sampleRate; + double m_sampleRate; int m_channels; double m_timeRatio; double m_pitchScale; - struct FftBand { - int fftSize; - float f0; - float f1; - FftBand(int _s, float _f0, float _f1) : - fftSize(_s), f0(_f0), f1(_f1) { } - }; - - struct PhaseLockBand { - int p; - float beta; - float f0; - float f1; - PhaseLockBand(int _p, float _beta, float _f0, float _f1) : - p(_p), beta(_beta), f0(_f0), f1(_f1) { } - }; - - struct Range { - bool present; - float f0; - float f1; - Range() : present(false), f0(0.f), f1(0.f) { } - }; - - struct Guidance { - FftBand fftBands[3]; - PhaseLockBand phaseLockBands[5]; - Range kick; - Range lowPercussive; - Range phaseReset; - Range highPercussive; - Range channelLock; - }; - struct ChannelScaleData { int fftSize; int bufSize; // size of every array here: fftSize/2 + 1 @@ -133,7 +100,7 @@ protected: BinSegmenter::Segmentation segmentation; BinSegmenter::Segmentation prevSegmentation; BinSegmenter::Segmentation nextSegmentation; - Guidance guidance; + Guide::Guidance guidance; }; std::map> m_ffts; From 42826e6a762976cd0273eeb0c37867cc7f8155de Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 19 May 2022 17:28:38 +0100 Subject: [PATCH 008/184] Obtain Guide configuration --- src/finer/Guide.h | 74 ++++++++++++++++++++++++++++++++----- src/finer/R3StretcherImpl.h | 12 ++++-- 2 files changed, 74 insertions(+), 12 deletions(-) diff --git a/src/finer/Guide.h b/src/finer/Guide.h index 9541970..6d5da67 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -36,6 +36,8 @@ public: float f1; FftBand(int _s, float _f0, float _f1) : fftSize(_s), f0(_f0), f1(_f1) { } + FftBand() : + fftSize(0), f0(0.f), f1(0.f) { } }; struct PhaseLockBand { @@ -45,13 +47,18 @@ public: float f1; PhaseLockBand(int _p, float _beta, float _f0, float _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; float f0; float f1; - Range() : present(false), f0(0.f), f1(0.f) { } + Range(bool _present, float _f0, float _f1) : + present(_present), f0(_f0), f1(_f1) { } + Range() : + present(false), f0(0.f), f1(0.f) { } }; struct Guidance { @@ -64,15 +71,54 @@ public: Range channelLock; }; - struct Parameters { + struct BandLimits { int fftSize; + float f0min; + float f0max; + float f1min; + float f1max; + BandLimits(int _fftSize, float _f0min, float _f0max, + float _f1min, float _f1max) : + fftSize(_fftSize), f0min(_f0min), f0max(_f0max), + f1min(_f1min), f1max(_f1max) { } + BandLimits() : + fftSize(0), f0min(0.f), f0max(0.f), f1min(0.f), f1max(0.f) { } + }; + + struct Configuration { + int classificationFftSize; + BandLimits fftBandLimits[3]; + Configuration(int _classificationFftSize) : + classificationFftSize(_classificationFftSize) { } + }; + + struct Parameters { double sampleRate; - Parameters(int _fftSize, double _sampleRate) : - fftSize(_fftSize), sampleRate(_sampleRate) { } + Parameters(double _sampleRate) : + sampleRate(_sampleRate) { } }; Guide(Parameters parameters) : - m_parameters(parameters) { } + m_parameters(parameters), + m_configuration(roundUp(int(ceil(parameters.sampleRate / 32.0)))), + m_defaultLower(700.0), m_defaultHigher(4800.0), + m_maxLower(1100.0), m_maxHigher(7000.0) + { + double rate = m_parameters.sampleRate; + m_configuration.fftBandLimits[0] = + BandLimits(roundUp(int(ceil(rate/16.0))), + 0.0, 0.0, m_defaultLower, m_maxLower); + m_configuration.fftBandLimits[1] = + BandLimits(roundUp(int(ceil(rate/32.0))), + m_defaultLower, m_maxLower, m_defaultHigher, m_maxHigher); + m_configuration.fftBandLimits[2] = + BandLimits(roundUp(int(ceil(rate/64.0))), + m_defaultHigher, m_maxHigher, rate/2.0, rate/2.0); + } + + const Configuration &getConfiguration() const { + return m_configuration; + } void calculate(double ratio, const float *const magnitudes, @@ -126,8 +172,12 @@ public: nextSegmentation.residualAbove); } - double higher = snapToTrough(4800.0, troughs); - double lower = snapToTrough(700.0, troughs); + double higher = snapToTrough(m_defaultHigher, troughs); + if (higher > m_maxHigher) higher = m_maxHigher; + + double lower = snapToTrough(m_defaultLower, troughs); + if (lower > m_maxLower) lower = m_maxLower; + double nyquist = m_parameters.sampleRate / 2.0; guidance.fftBands[0].fftSize = roundUp(int(ceil(nyquist/8.0))); @@ -167,14 +217,20 @@ public: protected: Parameters m_parameters; + Configuration m_configuration; + + double m_defaultLower; + double m_defaultHigher; + double m_maxLower; + double m_maxHigher; int binForFrequency(double f) const { - return int(round(f * double(m_parameters.fftSize) / + return int(round(f * double(m_configuration.classificationFftSize) / m_parameters.sampleRate)); } double frequencyForBin(int b) const { return (double(b) * m_parameters.sampleRate) - / double(m_parameters.fftSize); + / double(m_configuration.classificationFftSize); } // near-dupe with R2 RubberBandStretcher::Impl diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 64c4665..db99b1f 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -40,7 +40,11 @@ namespace RubberBand class R3StretcherImpl { public: - R3StretcherImpl(double sampleRate, int channels); + R3StretcherImpl(double sampleRate, int channels) : + m_sampleRate(sampleRate), m_channels(channels), + m_guide(Guide::Parameters(sampleRate)), + m_guideConfiguration(m_guide.getConfiguration()) + { } ~R3StretcherImpl(); void reset(); @@ -93,7 +97,7 @@ protected: ChannelScaleData(const ChannelScaleData &) =delete; ChannelScaleData &operator=(const ChannelScaleData &) =delete; }; - + struct ChannelData { std::map> scales; std::unique_ptr segmenter; @@ -101,10 +105,12 @@ protected: BinSegmenter::Segmentation prevSegmentation; BinSegmenter::Segmentation nextSegmentation; Guide::Guidance guidance; + }; std::map> m_ffts; - + Guide m_guide; + Guide::Configuration m_guideConfiguration; }; } From 9d646b9708a25bcc1294d664101fa86436ab2980 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 20 May 2022 15:29:52 +0100 Subject: [PATCH 009/184] Introduce phase advance --- src/common/FixedVector.h | 48 ++++++++ src/finer/BinClassifier.h | 2 + src/finer/Peak.h | 27 +++-- src/finer/PhaseAdvance.h | 218 ++++++++++++++++++++++++++++++++++++ src/finer/R3StretcherImpl.h | 66 ++++++----- 5 files changed, 317 insertions(+), 44 deletions(-) create mode 100644 src/common/FixedVector.h create mode 100644 src/finer/PhaseAdvance.h diff --git a/src/common/FixedVector.h b/src/common/FixedVector.h new file mode 100644 index 0000000..50cabb8 --- /dev/null +++ b/src/common/FixedVector.h @@ -0,0 +1,48 @@ +/* -*- 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_FIXED_VECTOR_H +#define RUBBERBAND_FIXED_VECTOR_H + +#include "Allocators.h" +#include + +namespace RubberBand +{ + +template +class FixedVector : public std::vector> +{ +public: + using std::vector>::vector; + +private: + FixedVector(const FixedVector &) =delete; + FixedVector(FixedVector &&) =delete; + FixedVector &operator=(const FixedVector &) =delete; + FixedVector &operator=(FixedVector &&) =delete; +}; + +} + +#endif diff --git a/src/finer/BinClassifier.h b/src/finer/BinClassifier.h index a92b009..82456de 100644 --- a/src/finer/BinClassifier.h +++ b/src/finer/BinClassifier.h @@ -138,6 +138,8 @@ protected: Parameters m_parameters; std::vector>> m_hFilters; std::unique_ptr> m_vFilter; + // We manage the queued frames through pointer swapping, hence + // bare pointers here float *m_hf; float *m_vf; RingBuffer m_vfQueue; diff --git a/src/finer/Peak.h b/src/finer/Peak.h index 7be4bd2..ce1c9ed 100644 --- a/src/finer/Peak.h +++ b/src/finer/Peak.h @@ -33,15 +33,21 @@ 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. + /** 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, @@ -50,11 +56,12 @@ public: 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. + /** 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, diff --git a/src/finer/PhaseAdvance.h b/src/finer/PhaseAdvance.h new file mode 100644 index 0000000..fb466d1 --- /dev/null +++ b/src/finer/PhaseAdvance.h @@ -0,0 +1,218 @@ +/* -*- 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" + +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) : + m_parameters(parameters), + m_blockSize(parameters.fftSize / 2 + 1), + m_peakPicker(m_blockSize) { + size_t ch = m_parameters.channels; + m_currentPeaks = allocate_and_zero_channels(ch, m_blockSize); + m_prevPeaks = allocate_and_zero_channels(ch, m_blockSize); + m_greatestChannel = allocate_and_zero(m_blockSize); + m_prevInPhase = allocate_and_zero_channels(ch, m_blockSize); + m_prevOutPhase = allocate_and_zero_channels(ch, m_blockSize); + m_unlocked = allocate_and_zero_channels(ch, m_blockSize); + } + + ~GuidedPhaseAdvance() { + size_t 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 advance(double *const *outPhase, + const float *const *mag, + const float *const *phase, + 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 = binForFrequency + (configuration.fftBandLimits[myFftBand].f0min); + int highest = binForFrequency + (configuration.fftBandLimits[myFftBand].f1max); + + 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); + int endBin = binForFrequency(band.f1); + if (startBin > highest || endBin < lowest) continue; + int count = endBin - startBin; + m_peakPicker.findNearestAndNextPeaks(mag[c], startBin, count, + band.p, m_currentPeaks[c]); + } + } + + 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); + 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 (inRange (f, g->highPercussive)) { + 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) { + 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] = ph; + } + } + + for (int c = 0; c < channels; ++c) { + for (int i = lowest; i <= highest; ++i) { + m_prevInPhase[c][i] = phase[c][i]; + } + } + for (int c = 0; c < channels; ++c) { + for (int i = lowest; i <= highest; ++i) { + m_prevOutPhase[c][i] = outPhase[c][i]; + } + } + + int **tmp = m_prevPeaks; + m_prevPeaks = m_currentPeaks; + m_currentPeaks = m_prevPeaks; + } + +protected: + Parameters m_parameters; + int m_blockSize; + Peak m_peakPicker; + int **m_currentPeaks; + int **m_prevPeaks; + int *m_greatestChannel; + float **m_prevInPhase; + double **m_prevOutPhase; + double **m_unlocked; + + int binForFrequency(double f) const { + return int(round(f * double(m_parameters.fftSize) / + m_parameters.sampleRate)); + } + double frequencyForBin(int b) const { + return (double(b) * m_parameters.sampleRate) + / double(m_parameters.fftSize); + } + bool inRange(double f, const Guide::Range &r) { + return r.present && f >= r.f0 && f < r.f1; + } +}; + +} + +#endif diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index db99b1f..d917242 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -30,9 +30,12 @@ #include "BinSegmenter.h" #include "Guide.h" #include "Peak.h" +#include "PhaseAdvance.h" #include "../common/FFT.h" +#include "../common/FixedVector.h" #include "../common/Allocators.h" +#include "../common/Window.h" namespace RubberBand { @@ -56,42 +59,29 @@ public: double getPitchScale() const; protected: - double m_sampleRate; - int m_channels; - - double m_timeRatio; - double m_pitchScale; - struct ChannelScaleData { int fftSize; - int bufSize; // size of every array here: fftSize/2 + 1 - float *mag; - float *phase; - int *nearestPeaks; - int *nearestTroughs; - float *prevOutMag; - float *prevOutPhase; - int *prevNearestPeaks; + int bufSize; // size of every freq-domain array here: fftSize/2 + 1 + FixedVector mag; + FixedVector phase; + FixedVector nearestPeaks; + FixedVector nearestTroughs; + FixedVector prevOutMag; + FixedVector prevOutPhase; + FixedVector prevNearestPeaks; + FixedVector timeDomainFrame; + Window analysisWindow; + Window synthesisWindow; ChannelScaleData(int _fftSize) : - fftSize(_fftSize), bufSize(_fftSize/2 + 1), - mag(allocate_and_zero(size_t(bufSize))), - phase(allocate_and_zero(size_t(bufSize))), - nearestPeaks(allocate_and_zero(size_t(bufSize))), - nearestTroughs(allocate_and_zero(size_t(bufSize))), - prevOutMag(allocate_and_zero(size_t(bufSize))), - prevOutPhase(allocate_and_zero(size_t(bufSize))), - prevNearestPeaks(allocate_and_zero(size_t(bufSize))) { } - - ~ChannelScaleData() { - deallocate(mag); - deallocate(phase); - deallocate(nearestPeaks); - deallocate(nearestTroughs); - deallocate(prevOutMag); - deallocate(prevOutPhase); - deallocate(prevNearestPeaks); - } + fftSize(_fftSize), bufSize(fftSize/2 + 1), + mag(bufSize, 0.f), phase(bufSize, 0.f), + nearestPeaks(bufSize, 0), nearestTroughs(bufSize, 0), + prevOutMag(bufSize, 0.f), prevOutPhase(bufSize, 0.f), + prevNearestPeaks(bufSize, 0), timeDomainFrame(fftSize, 0.f), + analysisWindow(HannWindow, fftSize), + synthesisWindow(HannWindow, fftSize/2) + { } private: ChannelScaleData(const ChannelScaleData &) =delete; @@ -105,9 +95,17 @@ protected: BinSegmenter::Segmentation prevSegmentation; BinSegmenter::Segmentation nextSegmentation; Guide::Guidance guidance; - + RingBuffer inbuf; + RingBuffer outbuf; }; - + + double m_sampleRate; + int m_channels; + + double m_timeRatio; + double m_pitchScale; + + std::vector m_channelData; std::map> m_ffts; Guide m_guide; Guide::Configuration m_guideConfiguration; From af97c70e69dc3a0dae785fb2c744cec313234d96 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 20 May 2022 16:48:44 +0100 Subject: [PATCH 010/184] Construct channel data --- meson.build | 2 +- src/finer/BinClassifier.h | 4 +-- src/finer/BinSegmenter.h | 2 ++ src/finer/Guide.h | 9 ++++-- src/finer/PhaseAdvance.h | 32 ++++++++++++++++--- src/finer/R3StretcherImpl.h | 64 ++++++++++++++++++++++++++++++------- src/temporary.cpp | 7 ++++ 7 files changed, 98 insertions(+), 22 deletions(-) diff --git a/meson.build b/meson.build index 0a1dfae..506bf4a 100644 --- a/meson.build +++ b/meson.build @@ -5,7 +5,7 @@ project( version: '2.0.2', license: 'GPL-2.0-or-later', default_options: [ - 'cpp_std=c++14', + 'cpp_std=c++11', 'buildtype=release', 'default_library=both', 'b_ndebug=if-release', diff --git a/src/finer/BinClassifier.h b/src/finer/BinClassifier.h index 82456de..cf2655c 100644 --- a/src/finer/BinClassifier.h +++ b/src/finer/BinClassifier.h @@ -66,6 +66,7 @@ public: BinClassifier(Parameters parameters) : m_parameters(parameters), + m_vFilter(new MovingMedian(m_parameters.verticalFilterLength)), m_vfQueue(parameters.horizontalFilterLag) { int n = m_parameters.binCount; @@ -75,9 +76,6 @@ public: (m_parameters.horizontalFilterLength)); } - m_vFilter = std::make_unique> - (m_parameters.verticalFilterLength); - m_hf = allocate_and_zero(n); m_vf = allocate_and_zero(n); diff --git a/src/finer/BinSegmenter.h b/src/finer/BinSegmenter.h index 6ae202c..a173ba0 100644 --- a/src/finer/BinSegmenter.h +++ b/src/finer/BinSegmenter.h @@ -37,6 +37,8 @@ public: 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) { } }; diff --git a/src/finer/Guide.h b/src/finer/Guide.h index 6d5da67..a1d6fac 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -87,9 +87,11 @@ public: struct Configuration { int classificationFftSize; + int longestFftSize; BandLimits fftBandLimits[3]; - Configuration(int _classificationFftSize) : - classificationFftSize(_classificationFftSize) { } + Configuration(int _classificationFftSize, int _longestFftSize) : + classificationFftSize(_classificationFftSize), + longestFftSize(_longestFftSize) { } }; struct Parameters { @@ -100,7 +102,8 @@ public: Guide(Parameters parameters) : m_parameters(parameters), - m_configuration(roundUp(int(ceil(parameters.sampleRate / 32.0)))), + m_configuration(roundUp(int(ceil(parameters.sampleRate / 32.0))), + roundUp(int(ceil(parameters.sampleRate / 16.0)))), m_defaultLower(700.0), m_defaultHigher(4800.0), m_maxLower(1100.0), m_maxHigher(7000.0) { diff --git a/src/finer/PhaseAdvance.h b/src/finer/PhaseAdvance.h index fb466d1..3015ab9 100644 --- a/src/finer/PhaseAdvance.h +++ b/src/finer/PhaseAdvance.h @@ -26,6 +26,9 @@ #include "Guide.h" +#include +#include + namespace RubberBand { @@ -36,14 +39,18 @@ public: int fftSize; double sampleRate; int channels; - Parameters(int _fftSize, double _sampleRate, int _channels) : - fftSize(_fftSize), sampleRate(_sampleRate), channels(_channels) { } + std::function logger; + Parameters(int _fftSize, double _sampleRate, int _channels, + std::function _log) : + fftSize(_fftSize), sampleRate(_sampleRate), + channels(_channels), logger(_log) { } }; GuidedPhaseAdvance(Parameters parameters) : m_parameters(parameters), m_blockSize(parameters.fftSize / 2 + 1), - m_peakPicker(m_blockSize) { + m_peakPicker(m_blockSize), + m_reported(false) { size_t ch = m_parameters.channels; m_currentPeaks = allocate_and_zero_channels(ch, m_blockSize); m_prevPeaks = allocate_and_zero_channels(ch, m_blockSize); @@ -89,6 +96,18 @@ public: (configuration.fftBandLimits[myFftBand].f0min); int highest = binForFrequency (configuration.fftBandLimits[myFftBand].f1max); + + if (!m_reported) { + std::ostringstream ostr; + ostr << "PhaseAdvance: fftSize = " << m_parameters.fftSize + << ": bins = " << bs << ", channels = " << channels + << ", inhop = "<< inhop << ", outhop = " << outhop + << ", ratio = " << ratio << std::endl; + ostr << "PhaseAdvance: lowest possible = " << lowest + << "Hz, highest = " << highest << "Hz" << std::endl; + m_parameters.logger(ostr.str()); + m_reported = true; + } for (int c = 0; c < channels; ++c) { for (int i = lowest; i <= highest; ++i) { @@ -184,9 +203,13 @@ public: } } + //!!! NB in the original we use a different value of p for + //!!! peak-picking the prior magnitudes - this isn't carried + //!!! over here + int **tmp = m_prevPeaks; m_prevPeaks = m_currentPeaks; - m_currentPeaks = m_prevPeaks; + m_currentPeaks = tmp; } protected: @@ -199,6 +222,7 @@ protected: float **m_prevInPhase; double **m_prevOutPhase; double **m_unlocked; + bool m_reported; int binForFrequency(double f) const { return int(round(f * double(m_parameters.fftSize) / diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index d917242..5a26cb1 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -24,9 +24,6 @@ #ifndef RUBBERBAND_R3_STRETCHERIMPL_H #define RUBBERBAND_R3_STRETCHERIMPL_H -#include -#include - #include "BinSegmenter.h" #include "Guide.h" #include "Peak.h" @@ -37,18 +34,52 @@ #include "../common/Allocators.h" #include "../common/Window.h" +#include +#include +#include + namespace RubberBand { class R3StretcherImpl { public: - R3StretcherImpl(double sampleRate, int channels) : - m_sampleRate(sampleRate), m_channels(channels), - m_guide(Guide::Parameters(sampleRate)), + struct Parameters { + double sampleRate; + int channels; + std::function logger; + Parameters(double _sampleRate, int _channels, + std::function _log = &logCerr) : + sampleRate(_sampleRate), channels(_channels), logger(_log) { } + }; + + R3StretcherImpl(Parameters parameters) : + m_parameters(parameters), + m_guide(Guide::Parameters(m_parameters.sampleRate)), m_guideConfiguration(m_guide.getConfiguration()) - { } - ~R3StretcherImpl(); + { + BinSegmenter::Parameters segmenterParameters + (m_guideConfiguration.classificationFftSize, + m_parameters.sampleRate); + BinClassifier::Parameters classifierParameters + (m_guideConfiguration.classificationFftSize / 2 + 1, + 9, 1, 10, 2.0, 2.0, 1.0e-7); + int ringBufferSize = m_guideConfiguration.longestFftSize * 2; + for (int c = 0; c < m_parameters.channels; ++c) { + m_channelData.push_back(std::make_shared + (segmenterParameters, + classifierParameters, + ringBufferSize)); + for (auto band: m_guideConfiguration.fftBandLimits) { + int fftSize = band.fftSize; + m_ffts[fftSize] = std::make_shared(fftSize); + m_channelData[c]->scales[fftSize] = + std::make_shared(fftSize); + } + } + } + + ~R3StretcherImpl() { } void reset(); @@ -97,18 +128,29 @@ protected: Guide::Guidance guidance; RingBuffer inbuf; RingBuffer outbuf; + ChannelData(BinSegmenter::Parameters segmenterParameters, + BinClassifier::Parameters classifierParameters, + int ringBufferSize) : + scales(), + segmenter(new BinSegmenter(segmenterParameters, + classifierParameters)), + segmentation(), prevSegmentation(), nextSegmentation(), + inbuf(ringBufferSize), outbuf(ringBufferSize) { } }; - double m_sampleRate; - int m_channels; + Parameters m_parameters; double m_timeRatio; double m_pitchScale; - std::vector m_channelData; + std::vector> m_channelData; std::map> m_ffts; Guide m_guide; Guide::Configuration m_guideConfiguration; + + static void logCerr(const std::string &message) { + std::cerr << "RubberBandStretcher: " << message << std::endl; + } }; } diff --git a/src/temporary.cpp b/src/temporary.cpp index 39d8493..9d21171 100644 --- a/src/temporary.cpp +++ b/src/temporary.cpp @@ -1 +1,8 @@ #include "finer/R3StretcherImpl.h" + +int main(int argc, char **argv) +{ + RubberBand::R3StretcherImpl::Parameters parameters(44100.0, 2); + RubberBand::R3StretcherImpl impl(parameters); +} + From 5cc48338202678a91d09b2b4b71ff655ecf1ac09 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 23 May 2022 15:04:34 +0100 Subject: [PATCH 011/184] Flesh out the implementation a bit --- meson.build | 1 + rubberband/RubberBandStretcher.h | 2 + src/RubberBandStretcher.cpp | 77 +++++---- src/common/Window.h | 4 + src/finer/PhaseAdvance.h | 3 + src/finer/R3StretcherImpl.cpp | 277 +++++++++++++++++++++++++++++++ src/finer/R3StretcherImpl.h | 92 +++++++--- 7 files changed, 408 insertions(+), 48 deletions(-) create mode 100644 src/finer/R3StretcherImpl.cpp diff --git a/meson.build b/meson.build index 506bf4a..e184a75 100644 --- a/meson.build +++ b/meson.build @@ -49,6 +49,7 @@ library_sources = [ 'src/common/sysutils.cpp', 'src/common/Thread.cpp', 'src/temporary.cpp', + 'src/finer/R3StretcherImpl.cpp', ] jni_sources = [ diff --git a/rubberband/RubberBandStretcher.h b/rubberband/RubberBandStretcher.h index b216048..4b8355b 100644 --- a/rubberband/RubberBandStretcher.h +++ b/rubberband/RubberBandStretcher.h @@ -81,6 +81,7 @@ namespace RubberBand { +class R3StretcherImpl;//!!! class RUBBERBAND_DLLEXPORT RubberBandStretcher @@ -765,6 +766,7 @@ public: protected: class Impl; Impl *m_d; + R3StretcherImpl *m_r3d; }; } diff --git a/src/RubberBandStretcher.cpp b/src/RubberBandStretcher.cpp index 5791a94..e6a3b01 100644 --- a/src/RubberBandStretcher.cpp +++ b/src/RubberBandStretcher.cpp @@ -22,6 +22,7 @@ */ #include "faster/StretcherImpl.h" +#include "finer/R3StretcherImpl.h" namespace RubberBand { @@ -32,184 +33,204 @@ RubberBandStretcher::RubberBandStretcher(size_t sampleRate, Options options, double initialTimeRatio, double initialPitchScale) : + /*!!! m_d(new Impl(sampleRate, channels, options, initialTimeRatio, initialPitchScale)) + */ + m_d(nullptr), + //!!! +logger + m_r3d(new R3StretcherImpl(R3StretcherImpl::Parameters + (sampleRate, channels))) { } RubberBandStretcher::~RubberBandStretcher() { delete m_d; + delete m_r3d; } void RubberBandStretcher::reset() { - m_d->reset(); + if (m_d) m_d->reset(); + else m_r3d->reset(); } void RubberBandStretcher::setTimeRatio(double ratio) { - m_d->setTimeRatio(ratio); + if (m_d) m_d->setTimeRatio(ratio); + else m_r3d->setTimeRatio(ratio); } void RubberBandStretcher::setPitchScale(double scale) { - m_d->setPitchScale(scale); + if (m_d) m_d->setPitchScale(scale); + else m_r3d->setPitchScale(scale); } double RubberBandStretcher::getTimeRatio() const { - return m_d->getTimeRatio(); + if (m_d) return m_d->getTimeRatio(); + else return m_r3d->getTimeRatio(); } double RubberBandStretcher::getPitchScale() const { - return m_d->getPitchScale(); + if (m_d) return m_d->getPitchScale(); + else return m_r3d->getPitchScale(); } size_t RubberBandStretcher::getLatency() const { - return m_d->getLatency(); + if (m_d) return m_d->getLatency(); + else return m_r3d->getLatency(); } void RubberBandStretcher::setTransientsOption(Options options) { - m_d->setTransientsOption(options); + if (m_d) m_d->setTransientsOption(options); } void RubberBandStretcher::setDetectorOption(Options options) { - m_d->setDetectorOption(options); + if (m_d) m_d->setDetectorOption(options); } void RubberBandStretcher::setPhaseOption(Options options) { - m_d->setPhaseOption(options); + if (m_d) m_d->setPhaseOption(options); } void RubberBandStretcher::setFormantOption(Options options) { - m_d->setFormantOption(options); + if (m_d) m_d->setFormantOption(options); } void RubberBandStretcher::setPitchOption(Options options) { - m_d->setPitchOption(options); + if (m_d) m_d->setPitchOption(options); } void RubberBandStretcher::setExpectedInputDuration(size_t samples) { - m_d->setExpectedInputDuration(samples); + if (m_d) m_d->setExpectedInputDuration(samples); } void RubberBandStretcher::setMaxProcessSize(size_t samples) { - m_d->setMaxProcessSize(samples); + if (m_d) m_d->setMaxProcessSize(samples); //!!! definitely need for r3d } void RubberBandStretcher::setKeyFrameMap(const std::map &mapping) { - m_d->setKeyFrameMap(mapping); + if (m_d) m_d->setKeyFrameMap(mapping); + //!!! } size_t RubberBandStretcher::getSamplesRequired() const { - return m_d->getSamplesRequired(); + if (m_d) return m_d->getSamplesRequired(); + else return m_r3d->getSamplesRequired(); } void RubberBandStretcher::study(const float *const *input, size_t samples, bool final) { - m_d->study(input, samples, final); + if (m_d) m_d->study(input, samples, final); + //!!! } void RubberBandStretcher::process(const float *const *input, size_t samples, bool final) { - m_d->process(input, samples, final); + if (m_d) m_d->process(input, samples, final); + else m_r3d->process(input, samples, final); } int RubberBandStretcher::available() const { - return m_d->available(); + if (m_d) return m_d->available(); + else return m_r3d->available(); } size_t RubberBandStretcher::retrieve(float *const *output, size_t samples) const { - return m_d->retrieve(output, samples); + if (m_d) return m_d->retrieve(output, samples); + else return m_r3d->retrieve(output, samples); } float RubberBandStretcher::getFrequencyCutoff(int n) const { - return m_d->getFrequencyCutoff(n); + if (m_d) return m_d->getFrequencyCutoff(n); } void RubberBandStretcher::setFrequencyCutoff(int n, float f) { - m_d->setFrequencyCutoff(n, f); + if (m_d) m_d->setFrequencyCutoff(n, f); } size_t RubberBandStretcher::getInputIncrement() const { - return m_d->getInputIncrement(); + if (m_d) return m_d->getInputIncrement(); } std::vector RubberBandStretcher::getOutputIncrements() const { - return m_d->getOutputIncrements(); + if (m_d) return m_d->getOutputIncrements(); } std::vector RubberBandStretcher::getPhaseResetCurve() const { - return m_d->getPhaseResetCurve(); + if (m_d) return m_d->getPhaseResetCurve(); } std::vector RubberBandStretcher::getExactTimePoints() const { - return m_d->getExactTimePoints(); + if (m_d) return m_d->getExactTimePoints(); } size_t RubberBandStretcher::getChannelCount() const { - return m_d->getChannelCount(); + if (m_d) return m_d->getChannelCount(); + else return m_r3d->getChannelCount(); } void RubberBandStretcher::calculateStretch() { - m_d->calculateStretch(); + if (m_d) m_d->calculateStretch(); } void RubberBandStretcher::setDebugLevel(int level) { - m_d->setDebugLevel(level); + if (m_d) m_d->setDebugLevel(level); } void diff --git a/src/common/Window.h b/src/common/Window.h index e4d6a05..d2cd2f6 100644 --- a/src/common/Window.h +++ b/src/common/Window.h @@ -79,6 +79,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); } diff --git a/src/finer/PhaseAdvance.h b/src/finer/PhaseAdvance.h index 3015ab9..7920289 100644 --- a/src/finer/PhaseAdvance.h +++ b/src/finer/PhaseAdvance.h @@ -235,6 +235,9 @@ protected: 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; }; } diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp new file mode 100644 index 0000000..5b5640d --- /dev/null +++ b/src/finer/R3StretcherImpl.cpp @@ -0,0 +1,277 @@ +/* -*- 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 "R3StretcherImpl.h" + +#include + +namespace RubberBand { + +void +R3StretcherImpl::setTimeRatio(double ratio) +{ + m_timeRatio = ratio; +} + +void +R3StretcherImpl::setPitchScale(double scale) +{ + m_pitchScale = scale; +} + +double +R3StretcherImpl::getTimeRatio() const +{ + return m_timeRatio; +} + +double +R3StretcherImpl::getPitchScale() const +{ + return m_pitchScale; +} + +size_t +R3StretcherImpl::getLatency() const +{ + return 0; //!!! +} + +size_t +R3StretcherImpl::getChannelCount() const +{ + return m_parameters.channels; +} + +void +R3StretcherImpl::reset() +{ + //!!! +} + +size_t +R3StretcherImpl::getSamplesRequired() const +{ + int longest = m_guideConfiguration.longestFftSize; + size_t rs = m_channelData[0]->inbuf->getReadSpace(); + if (rs < longest) { + return longest - rs; + } else { + return 0; + } +} + +void +R3StretcherImpl::process(const float *const *input, size_t samples, bool final) +{ + //!!! todo: final + + bool allConsumed = false; + + size_t ws = m_channelData[0]->inbuf->getWriteSpace(); + if (samples > ws) { + //!!! check this + m_parameters.logger("R3StretcherImpl::process: WARNING: Forced to increase input buffer size. Either setMaxProcessSize was not properly called or process is being called repeatedly without retrieve."); + size_t newSize = m_channelData[0]->inbuf->getSize() - ws + samples; + for (int c = 0; c < m_parameters.channels; ++c) { + m_channelData[c]->inbuf = + std::unique_ptr> + (m_channelData[c]->inbuf->resized(newSize)); + } + } + + for (int c = 0; c < m_parameters.channels; ++c) { + m_channelData[c]->inbuf->write(input[c], samples); + } + + consume(); +} + +int +R3StretcherImpl::available() const +{ + return int(m_channelData[0]->outbuf->getReadSpace()); +} + +size_t +R3StretcherImpl::retrieve(float *const *output, size_t samples) const +{ + size_t got = samples; + + for (size_t c = 0; c < m_parameters.channels; ++c) { + size_t gotHere = m_channelData[c]->outbuf->read(output[c], got); + if (gotHere < got) { + if (c > 0) { + m_parameters.logger("R3StretcherImpl::retrieve: WARNING: channel imbalance detected"); + } + got = gotHere; + } + } + + return got; +} + +void +R3StretcherImpl::consume() +{ + int inhop = 171, outhop = 256; //!!! + double ratio = double(outhop) / double(inhop); + + int longest = m_guideConfiguration.longestFftSize; + int classify = m_guideConfiguration.classificationFftSize; + + while (m_channelData[0]->inbuf->getReadSpace() >= longest && + m_channelData[0]->outbuf->getWriteSpace() >= outhop) { + + m_parameters.logger("consume looping"); + + for (int c = 0; c < m_parameters.channels; ++c) { + + auto cd = m_channelData[c]; + auto longestScale = cd->scales.at(longest); + + cd->inbuf->read(longestScale->timeDomainFrame.data(), longest); + + for (auto it: cd->scales) { + int fftSize = it.first; + auto scale = it.second; + if (fftSize == longest) continue; + int offset = (longest - fftSize) / 2; + m_scaleData.at(fftSize)->analysisWindow.cut + (longestScale->timeDomainFrame.data() + offset, + scale->timeDomainFrame.data()); + } + + m_scaleData.at(longest)->analysisWindow.cut + (longestScale->timeDomainFrame.data()); + } + + for (int c = 0; c < m_parameters.channels; ++c) { + + auto cd = m_channelData[c]; + + //!!! There are some aspects of scaling etc handled in bsq + //!!! that are not yet here + + for (auto it: cd->scales) { + int fftSize = it.first; + auto scale = it.second; + m_scaleData.at(fftSize)->fft.forwardPolar + (scale->timeDomainFrame.data(), + scale->mag.data(), + scale->phase.data()); + } + } + + for (int c = 0; c < m_parameters.channels; ++c) { + auto cd = m_channelData[c]; + auto classifyScale = cd->scales.at(classify); + cd->prevSegmentation = cd->segmentation; + cd->segmentation = cd->segmenter->segment(classifyScale->mag.data()); + m_troughPicker.findNearestAndNextPeaks + (classifyScale->mag.data(), 3, nullptr, + classifyScale->nextTroughs.data()); + m_guide.calculate(ratio, classifyScale->mag.data(), + classifyScale->nextTroughs.data(), + classifyScale->prevMag.data(), + cd->segmentation, + cd->prevSegmentation, + BinSegmenter::Segmentation(), //!!! + cd->guidance); + } + + for (auto it : m_channelData[0]->scales) { + int fftSize = it.first; + for (int c = 0; c < m_parameters.channels; ++c) { + auto cd = m_channelData[c]; + auto classifyScale = cd->scales.at(fftSize); + m_channelAssembly.mag[c] = classifyScale->mag.data(); + m_channelAssembly.phase[c] = classifyScale->phase.data(); + m_channelAssembly.guidance[c] = &cd->guidance; + m_channelAssembly.outPhase[c] = classifyScale->outPhase.data(); + } + m_scaleData.at(fftSize)->guided.advance + (m_channelAssembly.outPhase.data(), + m_channelAssembly.mag.data(), + m_channelAssembly.phase.data(), + m_guideConfiguration, + m_channelAssembly.guidance.data(), + inhop, + outhop); + } + + for (int c = 0; c < m_parameters.channels; ++c) { + for (auto it : m_channelData[c]->scales) { + auto scale = it.second; + int bufSize = scale->bufSize; + // copy to prevMag before filtering + v_copy(scale->prevMag.data(), scale->mag.data(), bufSize); + v_copy(scale->prevOutPhase.data(), scale->outPhase.data(), bufSize); + for (int i = 0; i < bufSize; ++i) { + scale->phase[i] = princarg(scale->outPhase[i]); + } + } + } + + //!!! + filter here + + for (int c = 0; c < m_parameters.channels; ++c) { + for (auto it : m_channelData[c]->scales) { + int fftSize = it.first; + auto scale = it.second; + auto scaleData = m_scaleData.at(fftSize); + int bufSize = scale->bufSize; + scaleData->fft.inversePolar(scale->mag.data(), + scale->phase.data(), + scale->timeDomainFrame.data()); + int synthesisWindowSize = scaleData->synthesisWindow.getSize(); + int fromOffset = (fftSize - synthesisWindowSize) / 2; + int toOffset = (longest - synthesisWindowSize) / 2; + //!!! not right - accumulator is of scale data size, not full longest size - we need offset when mixing into mixdown buffer below as well + scaleData->synthesisWindow.cutAndAdd + (scale->timeDomainFrame.data() + fromOffset, + scale->accumulator.data() + toOffset); + } + } + + for (int c = 0; c < m_parameters.channels; ++c) { + auto cd = m_channelData[c]; + v_zero(cd->mixdown.data(), outhop); + for (auto it : cd->scales) { + auto scale = it.second; + auto &acc = scale->accumulator; + v_add(cd->mixdown.data(), acc.data(), outhop); + int n = acc.size() - outhop; + v_move(acc.data(), acc.data() + outhop, n); + v_zero(acc.data() + n, outhop); + } + m_channelData[c]->outbuf->write(cd->mixdown.data(), outhop); + m_channelData[c]->inbuf->skip(inhop); + } + } + +} + + +} + diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 5a26cb1..703f17b 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -56,7 +56,9 @@ public: R3StretcherImpl(Parameters parameters) : m_parameters(parameters), m_guide(Guide::Parameters(m_parameters.sampleRate)), - m_guideConfiguration(m_guide.getConfiguration()) + m_guideConfiguration(m_guide.getConfiguration()), + m_channelAssembly(m_parameters.channels), + m_troughPicker(m_guideConfiguration.classificationFftSize / 2 + 1) { BinSegmenter::Parameters segmenterParameters (m_guideConfiguration.classificationFftSize, @@ -64,7 +66,9 @@ public: BinClassifier::Parameters classifierParameters (m_guideConfiguration.classificationFftSize / 2 + 1, 9, 1, 10, 2.0, 2.0, 1.0e-7); + int ringBufferSize = m_guideConfiguration.longestFftSize * 2; + for (int c = 0; c < m_parameters.channels; ++c) { m_channelData.push_back(std::make_shared (segmenterParameters, @@ -72,11 +76,18 @@ public: ringBufferSize)); for (auto band: m_guideConfiguration.fftBandLimits) { int fftSize = band.fftSize; - m_ffts[fftSize] = std::make_shared(fftSize); m_channelData[c]->scales[fftSize] = std::make_shared(fftSize); } } + + for (auto band: m_guideConfiguration.fftBandLimits) { + int fftSize = band.fftSize; + GuidedPhaseAdvance::Parameters guidedParameters + (fftSize, m_parameters.sampleRate, m_parameters.channels, + m_parameters.logger); + m_scaleData[fftSize] = std::make_shared(guidedParameters); + } } ~R3StretcherImpl() { } @@ -89,29 +100,39 @@ public: double getTimeRatio() const; double getPitchScale() const; + 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; + protected: struct ChannelScaleData { int fftSize; int bufSize; // size of every freq-domain array here: fftSize/2 + 1 + //!!! review later which of these we are actually using! + FixedVector timeDomainFrame; FixedVector mag; FixedVector phase; - FixedVector nearestPeaks; - FixedVector nearestTroughs; - FixedVector prevOutMag; + FixedVector outPhase; //!!! "advanced"? + FixedVector nextTroughs; //!!! not used in every scale + FixedVector prevMag; //!!! not used in every scale FixedVector prevOutPhase; - FixedVector prevNearestPeaks; - FixedVector timeDomainFrame; - Window analysisWindow; - Window synthesisWindow; + FixedVector accumulator; ChannelScaleData(int _fftSize) : - fftSize(_fftSize), bufSize(fftSize/2 + 1), - mag(bufSize, 0.f), phase(bufSize, 0.f), - nearestPeaks(bufSize, 0), nearestTroughs(bufSize, 0), - prevOutMag(bufSize, 0.f), prevOutPhase(bufSize, 0.f), - prevNearestPeaks(bufSize, 0), timeDomainFrame(fftSize, 0.f), - analysisWindow(HannWindow, fftSize), - synthesisWindow(HannWindow, fftSize/2) + fftSize(_fftSize), + bufSize(fftSize/2 + 1), + timeDomainFrame(fftSize, 0.f), + mag(bufSize, 0.f), + phase(bufSize, 0.f), + outPhase(bufSize, 0.f), + nextTroughs(bufSize, 0), + prevMag(bufSize, 0.f), + prevOutPhase(bufSize, 0.f), + accumulator(fftSize, 0.f) { } private: @@ -126,8 +147,9 @@ protected: BinSegmenter::Segmentation prevSegmentation; BinSegmenter::Segmentation nextSegmentation; Guide::Guidance guidance; - RingBuffer inbuf; - RingBuffer outbuf; + FixedVector mixdown; + std::unique_ptr> inbuf; + std::unique_ptr> outbuf; ChannelData(BinSegmenter::Parameters segmenterParameters, BinClassifier::Parameters classifierParameters, int ringBufferSize) : @@ -135,19 +157,49 @@ protected: segmenter(new BinSegmenter(segmenterParameters, classifierParameters)), segmentation(), prevSegmentation(), nextSegmentation(), - inbuf(ringBufferSize), outbuf(ringBufferSize) { } + mixdown(ringBufferSize, 0.f), //!!! could be much shorter (bound is the max outhop) + inbuf(new RingBuffer(ringBufferSize)), + outbuf(new RingBuffer(ringBufferSize)) { } }; + struct ChannelAssembly { + // Vectors of bare pointers, used to package container data + // from different channels into arguments for PhaseAdvance + FixedVector mag; + FixedVector phase; + FixedVector guidance; + FixedVector outPhase; + ChannelAssembly(int channels) : + mag(channels, nullptr), phase(channels, nullptr), + guidance(channels, nullptr), outPhase(channels, nullptr) { } + }; + + struct ScaleData { + FFT fft; + Window analysisWindow; + Window synthesisWindow; + GuidedPhaseAdvance guided; + ScaleData(GuidedPhaseAdvance::Parameters guidedParameters) : + fft(guidedParameters.fftSize), + analysisWindow(HannWindow, guidedParameters.fftSize), + synthesisWindow(HannWindow, guidedParameters.fftSize/2), + guided(guidedParameters) { } + }; + Parameters m_parameters; double m_timeRatio; double m_pitchScale; std::vector> m_channelData; - std::map> m_ffts; + std::map> m_scaleData; Guide m_guide; Guide::Configuration m_guideConfiguration; + ChannelAssembly m_channelAssembly; + Peak> m_troughPicker; + void consume(); + static void logCerr(const std::string &message) { std::cerr << "RubberBandStretcher: " << message << std::endl; } From fb9edfb20d665e35d7b1228fa4f051a35295d936 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 23 May 2022 16:45:41 +0100 Subject: [PATCH 012/184] Fix the consume logic so that it actually completes (though wrongly still, at this point) --- src/finer/R3StretcherImpl.cpp | 24 +++++++++++++++++++++--- src/finer/R3StretcherImpl.h | 4 +++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 5b5640d..f43f695 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -85,6 +85,12 @@ void R3StretcherImpl::process(const float *const *input, size_t samples, bool final) { //!!! todo: final + + m_parameters.logger("process called"); + if (final) { + m_parameters.logger("final = true"); + m_draining = true; + } bool allConsumed = false; @@ -110,12 +116,16 @@ R3StretcherImpl::process(const float *const *input, size_t samples, bool final) int R3StretcherImpl::available() const { - return int(m_channelData[0]->outbuf->getReadSpace()); + m_parameters.logger("available called"); + int av = int(m_channelData[0]->outbuf->getReadSpace()); + if (av == 0 && m_draining) return -1; + else return av; } size_t R3StretcherImpl::retrieve(float *const *output, size_t samples) const { + m_parameters.logger("retrieve called"); size_t got = samples; for (size_t c = 0; c < m_parameters.channels; ++c) { @@ -140,7 +150,7 @@ R3StretcherImpl::consume() int longest = m_guideConfiguration.longestFftSize; int classify = m_guideConfiguration.classificationFftSize; - while (m_channelData[0]->inbuf->getReadSpace() >= longest && + while ((m_draining || m_channelData[0]->inbuf->getReadSpace() >= longest) && m_channelData[0]->outbuf->getWriteSpace() >= outhop) { m_parameters.logger("consume looping"); @@ -150,7 +160,7 @@ R3StretcherImpl::consume() auto cd = m_channelData[c]; auto longestScale = cd->scales.at(longest); - cd->inbuf->read(longestScale->timeDomainFrame.data(), longest); + cd->inbuf->peek(longestScale->timeDomainFrame.data(), longest); for (auto it: cd->scales) { int fftSize = it.first; @@ -227,6 +237,7 @@ R3StretcherImpl::consume() // copy to prevMag before filtering v_copy(scale->prevMag.data(), scale->mag.data(), bufSize); v_copy(scale->prevOutPhase.data(), scale->outPhase.data(), bufSize); + //!!! seems wasteful for (int i = 0; i < bufSize; ++i) { scale->phase[i] = princarg(scale->outPhase[i]); } @@ -244,6 +255,7 @@ R3StretcherImpl::consume() scaleData->fft.inversePolar(scale->mag.data(), scale->phase.data(), scale->timeDomainFrame.data()); + /* int synthesisWindowSize = scaleData->synthesisWindow.getSize(); int fromOffset = (fftSize - synthesisWindowSize) / 2; int toOffset = (longest - synthesisWindowSize) / 2; @@ -251,6 +263,12 @@ R3StretcherImpl::consume() scaleData->synthesisWindow.cutAndAdd (scale->timeDomainFrame.data() + fromOffset, scale->accumulator.data() + toOffset); + */ + int synthesisWindowSize = scaleData->synthesisWindow.getSize(); + int offset = (fftSize - synthesisWindowSize) / 2; + scaleData->synthesisWindow.cutAndAdd + (scale->timeDomainFrame.data() + offset, + scale->accumulator.data()); } } diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 703f17b..707a0a7 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -58,7 +58,8 @@ public: m_guide(Guide::Parameters(m_parameters.sampleRate)), m_guideConfiguration(m_guide.getConfiguration()), m_channelAssembly(m_parameters.channels), - m_troughPicker(m_guideConfiguration.classificationFftSize / 2 + 1) + m_troughPicker(m_guideConfiguration.classificationFftSize / 2 + 1), + m_draining(false) { BinSegmenter::Parameters segmenterParameters (m_guideConfiguration.classificationFftSize, @@ -197,6 +198,7 @@ protected: Guide::Configuration m_guideConfiguration; ChannelAssembly m_channelAssembly; Peak> m_troughPicker; + bool m_draining; void consume(); From 9d82d58c368010fdaf4fa05f109eb1ab232ec2c6 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 23 May 2022 17:36:26 +0100 Subject: [PATCH 013/184] Scaling fixes --- src/finer/BinSegmenter.h | 1 + src/finer/R3StretcherImpl.cpp | 71 +++++++++++++++++++++-------------- 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/src/finer/BinSegmenter.h b/src/finer/BinSegmenter.h index a173ba0..e64ed39 100644 --- a/src/finer/BinSegmenter.h +++ b/src/finer/BinSegmenter.h @@ -114,6 +114,7 @@ protected: std::vector m_numeric; MovingMedian m_classFilter; + //!!! dupes int binForFrequency(double f) const { return int(round(f * double(m_parameters.fftSize) / m_parameters.sampleRate)); diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index f43f695..b7d1b2b 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -157,7 +157,7 @@ R3StretcherImpl::consume() for (int c = 0; c < m_parameters.channels; ++c) { - auto cd = m_channelData[c]; + auto cd = m_channelData.at(c); auto longestScale = cd->scales.at(longest); cd->inbuf->peek(longestScale->timeDomainFrame.data(), longest); @@ -178,10 +178,7 @@ R3StretcherImpl::consume() for (int c = 0; c < m_parameters.channels; ++c) { - auto cd = m_channelData[c]; - - //!!! There are some aspects of scaling etc handled in bsq - //!!! that are not yet here + auto cd = m_channelData.at(c); for (auto it: cd->scales) { int fftSize = it.first; @@ -190,11 +187,13 @@ R3StretcherImpl::consume() (scale->timeDomainFrame.data(), scale->mag.data(), scale->phase.data()); + v_scale(scale->mag.data(), 1.f / float(fftSize), + scale->mag.size()); } } for (int c = 0; c < m_parameters.channels; ++c) { - auto cd = m_channelData[c]; + auto cd = m_channelData.at(c); auto classifyScale = cd->scales.at(classify); cd->prevSegmentation = cd->segmentation; cd->segmentation = cd->segmenter->segment(classifyScale->mag.data()); @@ -213,7 +212,7 @@ R3StretcherImpl::consume() for (auto it : m_channelData[0]->scales) { int fftSize = it.first; for (int c = 0; c < m_parameters.channels; ++c) { - auto cd = m_channelData[c]; + auto cd = m_channelData.at(c); auto classifyScale = cd->scales.at(fftSize); m_channelAssembly.mag[c] = classifyScale->mag.data(); m_channelAssembly.phase[c] = classifyScale->phase.data(); @@ -231,7 +230,10 @@ R3StretcherImpl::consume() } for (int c = 0; c < m_parameters.channels; ++c) { - for (auto it : m_channelData[c]->scales) { + + auto cd = m_channelData.at(c); + + for (auto it : cd->scales) { auto scale = it.second; int bufSize = scale->bufSize; // copy to prevMag before filtering @@ -242,12 +244,37 @@ R3StretcherImpl::consume() scale->phase[i] = princarg(scale->outPhase[i]); } } - } - //!!! + filter here - - for (int c = 0; c < m_parameters.channels; ++c) { - for (auto it : m_channelData[c]->scales) { + for (const auto &band : cd->guidance.fftBands) { + int fftSize = band.fftSize; + auto scale = cd->scales.at(fftSize); + auto scaleData = m_scaleData.at(fftSize); + double factor = m_parameters.sampleRate / double(fftSize); + + //!!! messy and v slow, but leave it until we've + //!!! discovered whether we need a window accumulator + //!!! (we probably do) + int analysisWindowSize = scaleData->analysisWindow.getSize(); + int synthesisWindowSize = scaleData->synthesisWindow.getSize(); + int offset = (analysisWindowSize - synthesisWindowSize) / 2; + float winscale = 0.f; + for (int i = 0; i < synthesisWindowSize; ++i) { + winscale += scaleData->analysisWindow.getValue(i + offset) * + scaleData->synthesisWindow.getValue(i); + } + winscale = float(outhop) / winscale; + + for (int i = 0; i < fftSize/2 + 1; ++i) { + double f = double(i) * factor; + if (f >= band.f0 && f < band.f1) { + scale->mag[i] *= winscale; + } else { + scale->mag[i] = 0.f; + } + } + } + + for (auto it : cd->scales) { int fftSize = it.first; auto scale = it.second; auto scaleData = m_scaleData.at(fftSize); @@ -255,25 +282,14 @@ R3StretcherImpl::consume() scaleData->fft.inversePolar(scale->mag.data(), scale->phase.data(), scale->timeDomainFrame.data()); - /* - int synthesisWindowSize = scaleData->synthesisWindow.getSize(); - int fromOffset = (fftSize - synthesisWindowSize) / 2; - int toOffset = (longest - synthesisWindowSize) / 2; - //!!! not right - accumulator is of scale data size, not full longest size - we need offset when mixing into mixdown buffer below as well - scaleData->synthesisWindow.cutAndAdd - (scale->timeDomainFrame.data() + fromOffset, - scale->accumulator.data() + toOffset); - */ + int synthesisWindowSize = scaleData->synthesisWindow.getSize(); int offset = (fftSize - synthesisWindowSize) / 2; scaleData->synthesisWindow.cutAndAdd (scale->timeDomainFrame.data() + offset, scale->accumulator.data()); } - } - for (int c = 0; c < m_parameters.channels; ++c) { - auto cd = m_channelData[c]; v_zero(cd->mixdown.data(), outhop); for (auto it : cd->scales) { auto scale = it.second; @@ -283,11 +299,10 @@ R3StretcherImpl::consume() v_move(acc.data(), acc.data() + outhop, n); v_zero(acc.data() + n, outhop); } - m_channelData[c]->outbuf->write(cd->mixdown.data(), outhop); - m_channelData[c]->inbuf->skip(inhop); + cd->outbuf->write(cd->mixdown.data(), outhop); + cd->inbuf->skip(inhop); } } - } From 89c8683aaf24590fff584bfcd63a3e2c2f05fa74 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 23 May 2022 17:59:40 +0100 Subject: [PATCH 014/184] Calculate actual ratios and hops --- meson.build | 3 +- src/RubberBandStretcher.cpp | 5 +- src/{faster => common}/StretchCalculator.cpp | 2 +- src/{faster => common}/StretchCalculator.h | 0 src/faster/StretcherImpl.cpp | 2 +- src/faster/StretcherProcess.cpp | 2 +- src/finer/R3StretcherImpl.cpp | 50 ++++++++++++++++++-- src/finer/R3StretcherImpl.h | 23 ++++++++- vamp/RubberBandVampPlugin.cpp | 2 +- 9 files changed, 74 insertions(+), 15 deletions(-) rename src/{faster => common}/StretchCalculator.cpp (99%) rename src/{faster => common}/StretchCalculator.h (100%) diff --git a/meson.build b/meson.build index e184a75..f4b05f8 100644 --- a/meson.build +++ b/meson.build @@ -38,7 +38,6 @@ library_sources = [ 'src/faster/HighFrequencyAudioCurve.cpp', 'src/faster/SilentAudioCurve.cpp', 'src/faster/PercussiveAudioCurve.cpp', - 'src/faster/StretchCalculator.cpp', 'src/faster/StretcherChannelData.cpp', 'src/faster/StretcherImpl.cpp', 'src/faster/StretcherProcess.cpp', @@ -46,9 +45,9 @@ library_sources = [ 'src/common/Resampler.cpp', 'src/common/FFT.cpp', 'src/common/Allocators.cpp', + 'src/common/StretchCalculator.cpp', 'src/common/sysutils.cpp', 'src/common/Thread.cpp', - 'src/temporary.cpp', 'src/finer/R3StretcherImpl.cpp', ] diff --git a/src/RubberBandStretcher.cpp b/src/RubberBandStretcher.cpp index e6a3b01..4a6d130 100644 --- a/src/RubberBandStretcher.cpp +++ b/src/RubberBandStretcher.cpp @@ -39,8 +39,9 @@ RubberBandStretcher::RubberBandStretcher(size_t sampleRate, */ m_d(nullptr), //!!! +logger - m_r3d(new R3StretcherImpl(R3StretcherImpl::Parameters - (sampleRate, channels))) + m_r3d(new R3StretcherImpl(R3StretcherImpl::Parameters(sampleRate, channels), + initialTimeRatio, + initialPitchScale)) { } diff --git a/src/faster/StretchCalculator.cpp b/src/common/StretchCalculator.cpp similarity index 99% rename from src/faster/StretchCalculator.cpp rename to src/common/StretchCalculator.cpp index 87580e8..071e56b 100644 --- a/src/faster/StretchCalculator.cpp +++ b/src/common/StretchCalculator.cpp @@ -30,7 +30,7 @@ #include #include -#include "../common/sysutils.h" +#include "sysutils.h" namespace RubberBand { diff --git a/src/faster/StretchCalculator.h b/src/common/StretchCalculator.h similarity index 100% rename from src/faster/StretchCalculator.h rename to src/common/StretchCalculator.h diff --git a/src/faster/StretcherImpl.cpp b/src/faster/StretcherImpl.cpp index a23ebbc..4cc760b 100644 --- a/src/faster/StretcherImpl.cpp +++ b/src/faster/StretcherImpl.cpp @@ -27,9 +27,9 @@ #include "HighFrequencyAudioCurve.h" #include "SilentAudioCurve.h" #include "CompoundAudioCurve.h" -#include "StretchCalculator.h" #include "StretcherChannelData.h" +#include "../common/StretchCalculator.h" #include "../common/Resampler.h" #include "../common/Profiler.h" #include "../common/sysutils.h" diff --git a/src/faster/StretcherProcess.cpp b/src/faster/StretcherProcess.cpp index cb79b2a..c5a8b60 100644 --- a/src/faster/StretcherProcess.cpp +++ b/src/faster/StretcherProcess.cpp @@ -22,9 +22,9 @@ */ #include "StretcherImpl.h" -#include "StretchCalculator.h" #include "StretcherChannelData.h" +#include "../common/StretchCalculator.h" #include "../common/Resampler.h" #include "../common/Profiler.h" #include "../common/VectorOps.h" diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index b7d1b2b..de9ff85 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -31,12 +31,39 @@ void R3StretcherImpl::setTimeRatio(double ratio) { m_timeRatio = ratio; + calculateHop(); } void R3StretcherImpl::setPitchScale(double scale) { m_pitchScale = scale; + calculateHop(); +} + +void +R3StretcherImpl::calculateHop() +{ + double ratio = getEffectiveRatio(); + double proposedOuthop = 256; + + if (ratio > 1.0) { + double inhop = proposedOuthop / ratio; + if (inhop < 1.0) { + m_parameters.logger("WARNING: Extreme ratio yields ideal inhop < 1, results may be suspect"); + m_inhop = 1; + } else { + m_inhop = int(round(inhop)); + } + } else { + double inhop = std::min(proposedOuthop / ratio, 340.0); + m_inhop = int(round(inhop)); + } + + std::ostringstream str; + str << "R3StretcherImpl::calculateHop: for effective ratio " << ratio + << " calculated (typical) inhop of " << m_inhop << std::endl; + m_parameters.logger(str.str()); } double @@ -144,16 +171,28 @@ R3StretcherImpl::retrieve(float *const *output, size_t samples) const void R3StretcherImpl::consume() { - int inhop = 171, outhop = 256; //!!! - double ratio = double(outhop) / double(inhop); + double ratio = getEffectiveRatio(); int longest = m_guideConfiguration.longestFftSize; int classify = m_guideConfiguration.classificationFftSize; + + int outhop = m_calculator->calculateSingle(ratio, + 1.0 / m_pitchScale, + 1.f, + m_inhop, + longest, + longest); + + double instantaneousRatio = double(outhop) / double(m_inhop); while ((m_draining || m_channelData[0]->inbuf->getReadSpace() >= longest) && m_channelData[0]->outbuf->getWriteSpace() >= outhop) { m_parameters.logger("consume looping"); + + if (m_draining && m_channelData[0]->inbuf->getReadSpace() == 0) { + break; + } for (int c = 0; c < m_parameters.channels; ++c) { @@ -200,7 +239,8 @@ R3StretcherImpl::consume() m_troughPicker.findNearestAndNextPeaks (classifyScale->mag.data(), 3, nullptr, classifyScale->nextTroughs.data()); - m_guide.calculate(ratio, classifyScale->mag.data(), + m_guide.calculate(instantaneousRatio, + classifyScale->mag.data(), classifyScale->nextTroughs.data(), classifyScale->prevMag.data(), cd->segmentation, @@ -225,7 +265,7 @@ R3StretcherImpl::consume() m_channelAssembly.phase.data(), m_guideConfiguration, m_channelAssembly.guidance.data(), - inhop, + m_inhop, outhop); } @@ -300,7 +340,7 @@ R3StretcherImpl::consume() v_zero(acc.data() + n, outhop); } cd->outbuf->write(cd->mixdown.data(), outhop); - cd->inbuf->skip(inhop); + cd->inbuf->skip(m_inhop); } } } diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 707a0a7..afd47d8 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -29,6 +29,7 @@ #include "Peak.h" #include "PhaseAdvance.h" +#include "../common/StretchCalculator.h" #include "../common/FFT.h" #include "../common/FixedVector.h" #include "../common/Allocators.h" @@ -53,12 +54,17 @@ public: sampleRate(_sampleRate), channels(_channels), logger(_log) { } }; - R3StretcherImpl(Parameters parameters) : + R3StretcherImpl(Parameters parameters, + double initialTimeRatio, + double initialPitchScale) : m_parameters(parameters), + m_timeRatio(initialTimeRatio), + m_pitchScale(initialPitchScale), m_guide(Guide::Parameters(m_parameters.sampleRate)), m_guideConfiguration(m_guide.getConfiguration()), m_channelAssembly(m_parameters.channels), m_troughPicker(m_guideConfiguration.classificationFftSize / 2 + 1), + m_inhop(1), m_draining(false) { BinSegmenter::Parameters segmenterParameters @@ -89,6 +95,12 @@ public: m_parameters.logger); m_scaleData[fftSize] = std::make_shared(guidedParameters); } + + m_calculator = std::unique_ptr + (new StretchCalculator(int(round(m_parameters.sampleRate)), //!!! which is a double... + 1, false)); // no fixed inputIncrement + + calculateHop(); } ~R3StretcherImpl() { } @@ -191,16 +203,23 @@ protected: double m_timeRatio; double m_pitchScale; - + std::vector> m_channelData; std::map> m_scaleData; Guide m_guide; Guide::Configuration m_guideConfiguration; ChannelAssembly m_channelAssembly; Peak> m_troughPicker; + std::unique_ptr m_calculator; + int m_inhop; bool m_draining; void consume(); + void calculateHop(); + + double getEffectiveRatio() const { + return m_timeRatio * m_pitchScale; + } static void logCerr(const std::string &message) { std::cerr << "RubberBandStretcher: " << message << std::endl; diff --git a/vamp/RubberBandVampPlugin.cpp b/vamp/RubberBandVampPlugin.cpp index a3dbd6c..6a8ca3a 100644 --- a/vamp/RubberBandVampPlugin.cpp +++ b/vamp/RubberBandVampPlugin.cpp @@ -23,7 +23,7 @@ #include "RubberBandVampPlugin.h" -#include "faster/StretchCalculator.h" +#include "common/StretchCalculator.h" #include "common/sysutils.h" #include From cd0ee3e4f63982cea739d990c43a969a29761bf7 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 23 May 2022 20:55:56 +0100 Subject: [PATCH 015/184] Tidy, and avoid warnings from over-reading from ring buffer --- src/finer/R3StretcherImpl.cpp | 63 ++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index de9ff85..7bb71dc 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -113,9 +113,9 @@ R3StretcherImpl::process(const float *const *input, size_t samples, bool final) { //!!! todo: final - m_parameters.logger("process called"); +//!!! m_parameters.logger("process called"); if (final) { - m_parameters.logger("final = true"); +// m_parameters.logger("final = true"); m_draining = true; } @@ -143,7 +143,7 @@ R3StretcherImpl::process(const float *const *input, size_t samples, bool final) int R3StretcherImpl::available() const { - m_parameters.logger("available called"); +//!!! m_parameters.logger("available called"); int av = int(m_channelData[0]->outbuf->getReadSpace()); if (av == 0 && m_draining) return -1; else return av; @@ -152,7 +152,7 @@ R3StretcherImpl::available() const size_t R3StretcherImpl::retrieve(float *const *output, size_t samples) const { - m_parameters.logger("retrieve called"); +//!!! m_parameters.logger("retrieve called"); size_t got = samples; for (size_t c = 0; c < m_parameters.channels; ++c) { @@ -184,22 +184,34 @@ R3StretcherImpl::consume() longest); double instantaneousRatio = double(outhop) / double(m_inhop); - - while ((m_draining || m_channelData[0]->inbuf->getReadSpace() >= longest) && - m_channelData[0]->outbuf->getWriteSpace() >= outhop) { - m_parameters.logger("consume looping"); + while (m_channelData.at(0)->outbuf->getWriteSpace() >= outhop) { - if (m_draining && m_channelData[0]->inbuf->getReadSpace() == 0) { - break; +//!!! m_parameters.logger("consume looping"); + + int readSpace = m_channelData.at(0)->inbuf->getReadSpace(); + if (readSpace < longest) { + if (m_draining) { + if (readSpace == 0) { + break; + } + } else { + break; + } } - + for (int c = 0; c < m_parameters.channels; ++c) { auto cd = m_channelData.at(c); auto longestScale = cd->scales.at(longest); - - cd->inbuf->peek(longestScale->timeDomainFrame.data(), longest); + auto buf = longestScale->timeDomainFrame.data(); + + if (readSpace < longest) { + v_zero(buf, longest); + cd->inbuf->peek(buf, readSpace); + } else { + cd->inbuf->peek(buf, longest); + } for (auto it: cd->scales) { int fftSize = it.first; @@ -207,17 +219,10 @@ R3StretcherImpl::consume() if (fftSize == longest) continue; int offset = (longest - fftSize) / 2; m_scaleData.at(fftSize)->analysisWindow.cut - (longestScale->timeDomainFrame.data() + offset, - scale->timeDomainFrame.data()); + (buf + offset, scale->timeDomainFrame.data()); } - m_scaleData.at(longest)->analysisWindow.cut - (longestScale->timeDomainFrame.data()); - } - - for (int c = 0; c < m_parameters.channels; ++c) { - - auto cd = m_channelData.at(c); + m_scaleData.at(longest)->analysisWindow.cut(buf); for (auto it: cd->scales) { int fftSize = it.first; @@ -229,13 +234,11 @@ R3StretcherImpl::consume() v_scale(scale->mag.data(), 1.f / float(fftSize), scale->mag.size()); } - } - for (int c = 0; c < m_parameters.channels; ++c) { - auto cd = m_channelData.at(c); auto classifyScale = cd->scales.at(classify); cd->prevSegmentation = cd->segmentation; - cd->segmentation = cd->segmenter->segment(classifyScale->mag.data()); + cd->segmentation = + cd->segmenter->segment(classifyScale->mag.data()); m_troughPicker.findNearestAndNextPeaks (classifyScale->mag.data(), 3, nullptr, classifyScale->nextTroughs.data()); @@ -340,7 +343,13 @@ R3StretcherImpl::consume() v_zero(acc.data() + n, outhop); } cd->outbuf->write(cd->mixdown.data(), outhop); - cd->inbuf->skip(m_inhop); + + if (readSpace < m_inhop) { + // This should happen only when draining + cd->inbuf->skip(readSpace); + } else { + cd->inbuf->skip(m_inhop); + } } } } From 2b401e5cbe278ea6a45472784ed61bc2ead5d43c Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 24 May 2022 12:00:54 +0100 Subject: [PATCH 016/184] Overlap/add fixes. Some phase problems still here --- src/common/StretchCalculator.cpp | 7 ++++- src/common/StretchCalculator.h | 1 + src/finer/Guide.h | 42 +++++++++++++++++++++++------ src/finer/PhaseAdvance.h | 12 ++++++--- src/finer/R3StretcherImpl.cpp | 45 ++++++++++++++++++++++---------- src/finer/R3StretcherImpl.h | 11 ++++---- 6 files changed, 86 insertions(+), 32 deletions(-) diff --git a/src/common/StretchCalculator.cpp b/src/common/StretchCalculator.cpp index 071e56b..2ad0eea 100644 --- a/src/common/StretchCalculator.cpp +++ b/src/common/StretchCalculator.cpp @@ -43,6 +43,7 @@ StretchCalculator::StretchCalculator(size_t sampleRate, m_prevDf(0), m_prevRatio(1.0), m_prevTimeRatio(1.0), + m_justReset(true), m_transientAmnesty(0), m_debugLevel(0), m_useHardPeaks(useHardPeaks), @@ -371,7 +372,9 @@ StretchCalculator::calculateSingle(double timeRatio, // / pitchScale if resampling is happening after stretching). So // the overall ratio is timeRatio / effectivePitchRatio. - bool ratioChanged = (ratio != m_prevRatio); + bool ratioChanged = (!m_justReset) && (ratio != m_prevRatio); + m_justReset = false; + if (ratioChanged) { // Reset our frame counters from the ratio change. @@ -535,6 +538,8 @@ StretchCalculator::reset() m_outFrameCounter = 0.0; m_transientAmnesty = 0; m_keyFrameMap.clear(); + + m_justReset = true; } std::vector diff --git a/src/common/StretchCalculator.h b/src/common/StretchCalculator.h index 13334e1..f7a8112 100644 --- a/src/common/StretchCalculator.h +++ b/src/common/StretchCalculator.h @@ -98,6 +98,7 @@ protected: 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; diff --git a/src/finer/Guide.h b/src/finer/Guide.h index a1d6fac..5ff2df4 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -24,6 +24,9 @@ #ifndef RUBBERBAND_GUIDE_H #define RUBBERBAND_GUIDE_H +#include +#include + namespace RubberBand { @@ -86,24 +89,31 @@ public: }; struct Configuration { - int classificationFftSize; int longestFftSize; + int shortestFftSize; + int classificationFftSize; BandLimits fftBandLimits[3]; - Configuration(int _classificationFftSize, int _longestFftSize) : - classificationFftSize(_classificationFftSize), - longestFftSize(_longestFftSize) { } + Configuration(int _longestFftSize, int _shortestFftSize, + int _classificationFftSize) : + longestFftSize(_longestFftSize), + shortestFftSize(_shortestFftSize), + classificationFftSize(_classificationFftSize) { } }; struct Parameters { double sampleRate; - Parameters(double _sampleRate) : - sampleRate(_sampleRate) { } + std::function logger; + Parameters(double _sampleRate, + std::function _log) : + sampleRate(_sampleRate), + logger(_log) { } }; Guide(Parameters parameters) : m_parameters(parameters), - m_configuration(roundUp(int(ceil(parameters.sampleRate / 32.0))), - roundUp(int(ceil(parameters.sampleRate / 16.0)))), + m_configuration(roundUp(int(ceil(parameters.sampleRate / 16.0))), + roundUp(int(ceil(parameters.sampleRate / 64.0))), + roundUp(int(ceil(parameters.sampleRate / 32.0)))), m_defaultLower(700.0), m_defaultHigher(4800.0), m_maxLower(1100.0), m_maxHigher(7000.0) { @@ -216,6 +226,22 @@ public: guidance.phaseLockBands[3].beta = betaFor(10000.0, ratio); guidance.phaseLockBands[3].f0 = higher; guidance.phaseLockBands[3].f1 = nyquist; + + 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()); } protected: diff --git a/src/finer/PhaseAdvance.h b/src/finer/PhaseAdvance.h index 7920289..b783666 100644 --- a/src/finer/PhaseAdvance.h +++ b/src/finer/PhaseAdvance.h @@ -96,15 +96,18 @@ public: (configuration.fftBandLimits[myFftBand].f0min); int highest = binForFrequency (configuration.fftBandLimits[myFftBand].f1max); - + if (!m_reported) { std::ostringstream ostr; ostr << "PhaseAdvance: fftSize = " << m_parameters.fftSize << ": bins = " << bs << ", channels = " << channels << ", inhop = "<< inhop << ", outhop = " << outhop << ", ratio = " << ratio << std::endl; - ostr << "PhaseAdvance: lowest possible = " << lowest - << "Hz, highest = " << highest << "Hz" << std::endl; + ostr << "PhaseAdvance: lowest possible bin = " << lowest + << " (" << configuration.fftBandLimits[myFftBand].f0min + << "Hz), highest = " << highest + << " (" << configuration.fftBandLimits[myFftBand].f1max + << "Hz)" << std::endl; m_parameters.logger(ostr.str()); m_reported = true; } @@ -160,9 +163,10 @@ public: ++phaseLockBand; } double ph = 0.0; + /* if (inRange(f, g->phaseReset) || inRange(f, g->kick)) { ph = phase[c][i]; - } else if (inRange (f, g->highPercussive)) { + } else */ if (inRange (f, g->highPercussive)) { ph = m_unlocked[c][i]; } else { int peak = m_currentPeaks[c][i]; diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 7bb71dc..f903c82 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -176,6 +176,8 @@ R3StretcherImpl::consume() int longest = m_guideConfiguration.longestFftSize; int classify = m_guideConfiguration.classificationFftSize; + m_calculator->setDebugLevel(3); + int outhop = m_calculator->calculateSingle(ratio, 1.0 / m_pitchScale, 1.f, @@ -183,6 +185,8 @@ R3StretcherImpl::consume() longest, longest); + std::cout << "outhop = " << outhop << std::endl; + double instantaneousRatio = double(outhop) / double(m_inhop); while (m_channelData.at(0)->outbuf->getWriteSpace() >= outhop) { @@ -292,7 +296,6 @@ R3StretcherImpl::consume() int fftSize = band.fftSize; auto scale = cd->scales.at(fftSize); auto scaleData = m_scaleData.at(fftSize); - double factor = m_parameters.sampleRate / double(fftSize); //!!! messy and v slow, but leave it until we've //!!! discovered whether we need a window accumulator @@ -307,9 +310,11 @@ R3StretcherImpl::consume() } winscale = float(outhop) / winscale; + double factor = m_parameters.sampleRate / double(fftSize); for (int i = 0; i < fftSize/2 + 1; ++i) { double f = double(i) * factor; if (f >= band.f0 && f < band.f1) { + //!!! check the mod 2 bit from stretch-fn scale->mag[i] *= winscale; } else { scale->mag[i] = 0.f; @@ -321,29 +326,41 @@ R3StretcherImpl::consume() int fftSize = it.first; auto scale = it.second; auto scaleData = m_scaleData.at(fftSize); - int bufSize = scale->bufSize; + scaleData->fft.inversePolar(scale->mag.data(), scale->phase.data(), scale->timeDomainFrame.data()); int synthesisWindowSize = scaleData->synthesisWindow.getSize(); - int offset = (fftSize - synthesisWindowSize) / 2; - scaleData->synthesisWindow.cutAndAdd - (scale->timeDomainFrame.data() + offset, - scale->accumulator.data()); - } + int fromOffset = (fftSize - synthesisWindowSize) / 2; + int toOffset = (m_guideConfiguration.longestFftSize - + synthesisWindowSize) / 2; + + scaleData->synthesisWindow.cutAndAdd + (scale->timeDomainFrame.data() + fromOffset, + scale->accumulator.data() + toOffset); + } + + auto mixptr = cd->mixdown.data(); + v_zero(mixptr, outhop); - v_zero(cd->mixdown.data(), outhop); for (auto it : cd->scales) { auto scale = it.second; - auto &acc = scale->accumulator; - v_add(cd->mixdown.data(), acc.data(), outhop); - int n = acc.size() - outhop; - v_move(acc.data(), acc.data() + outhop, n); - v_zero(acc.data() + n, outhop); + v_add(mixptr, scale->accumulator.data(), outhop); } - cd->outbuf->write(cd->mixdown.data(), outhop); + cd->outbuf->write(mixptr, outhop); + + for (auto it : cd->scales) { + int fftSize = it.first; + auto scale = it.second; + auto accptr = scale->accumulator.data(); + + int n = scale->accumulator.size() - outhop; + v_move(accptr, accptr + outhop, n); + v_zero(accptr + n, outhop); + } + if (readSpace < m_inhop) { // This should happen only when draining cd->inbuf->skip(readSpace); diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index afd47d8..7e31432 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -60,7 +60,7 @@ public: m_parameters(parameters), m_timeRatio(initialTimeRatio), m_pitchScale(initialPitchScale), - m_guide(Guide::Parameters(m_parameters.sampleRate)), + m_guide(Guide::Parameters(m_parameters.sampleRate, parameters.logger)), m_guideConfiguration(m_guide.getConfiguration()), m_channelAssembly(m_parameters.channels), m_troughPicker(m_guideConfiguration.classificationFftSize / 2 + 1), @@ -84,7 +84,8 @@ public: for (auto band: m_guideConfiguration.fftBandLimits) { int fftSize = band.fftSize; m_channelData[c]->scales[fftSize] = - std::make_shared(fftSize); + std::make_shared + (fftSize, m_guideConfiguration.longestFftSize); } } @@ -135,7 +136,7 @@ protected: FixedVector prevOutPhase; FixedVector accumulator; - ChannelScaleData(int _fftSize) : + ChannelScaleData(int _fftSize, int _longestFftSize) : fftSize(_fftSize), bufSize(fftSize/2 + 1), timeDomainFrame(fftSize, 0.f), @@ -145,7 +146,7 @@ protected: nextTroughs(bufSize, 0), prevMag(bufSize, 0.f), prevOutPhase(bufSize, 0.f), - accumulator(fftSize, 0.f) + accumulator(_longestFftSize, 0.f) { } private: @@ -170,7 +171,7 @@ protected: segmenter(new BinSegmenter(segmenterParameters, classifierParameters)), segmentation(), prevSegmentation(), nextSegmentation(), - mixdown(ringBufferSize, 0.f), //!!! could be much shorter (bound is the max outhop) + mixdown(ringBufferSize, 0.f), //!!! could be shorter (bound is the max fft size I think) inbuf(new RingBuffer(ringBufferSize)), outbuf(new RingBuffer(ringBufferSize)) { } }; From 49ac04ceabe249dcbe366b848bf28ff50f7509f5 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 24 May 2022 15:21:13 +0100 Subject: [PATCH 017/184] Some work on phase updates --- src/finer/Guide.h | 20 +++++++-------- src/finer/Peak.h | 2 +- src/finer/PhaseAdvance.h | 47 +++++++++++++++++++++++++++-------- src/finer/R3StretcherImpl.cpp | 7 +++++- 4 files changed, 54 insertions(+), 22 deletions(-) diff --git a/src/finer/Guide.h b/src/finer/Guide.h index 5ff2df4..40313bc 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -77,15 +77,11 @@ public: struct BandLimits { int fftSize; float f0min; - float f0max; - float f1min; float f1max; - BandLimits(int _fftSize, float _f0min, float _f0max, - float _f1min, float _f1max) : - fftSize(_fftSize), f0min(_f0min), f0max(_f0max), - f1min(_f1min), f1max(_f1max) { } + BandLimits(int _fftSize, float _f0min, float _f1max) : + fftSize(_fftSize), f0min(_f0min), f1max(_f1max) { } BandLimits() : - fftSize(0), f0min(0.f), f0max(0.f), f1min(0.f), f1max(0.f) { } + fftSize(0), f0min(0.f), f1max(0.f) { } }; struct Configuration { @@ -114,19 +110,21 @@ public: 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(350.0), m_minHigher(2400.0), m_defaultLower(700.0), m_defaultHigher(4800.0), m_maxLower(1100.0), m_maxHigher(7000.0) { double rate = m_parameters.sampleRate; + double nyquist = rate / 2.0; m_configuration.fftBandLimits[0] = BandLimits(roundUp(int(ceil(rate/16.0))), - 0.0, 0.0, m_defaultLower, m_maxLower); + 0.0, m_maxLower); m_configuration.fftBandLimits[1] = BandLimits(roundUp(int(ceil(rate/32.0))), - m_defaultLower, m_maxLower, m_defaultHigher, m_maxHigher); + m_minLower, m_maxHigher); m_configuration.fftBandLimits[2] = BandLimits(roundUp(int(ceil(rate/64.0))), - m_defaultHigher, m_maxHigher, rate/2.0, rate/2.0); + m_minHigher, rate/2.0); } const Configuration &getConfiguration() const { @@ -248,6 +246,8 @@ protected: Parameters m_parameters; Configuration m_configuration; + double m_minLower; + double m_minHigher; double m_defaultLower; double m_defaultHigher; double m_maxLower; diff --git a/src/finer/Peak.h b/src/finer/Peak.h index ce1c9ed..87a224f 100644 --- a/src/finer/Peak.h +++ b/src/finer/Peak.h @@ -110,7 +110,7 @@ public: if (j == 0) { nearest[i] = np; } else { - if (np - i < i - pp) { + if (np - i <= i - pp) { nearest[i] = np; } else { nearest[i] = pp; diff --git a/src/finer/PhaseAdvance.h b/src/finer/PhaseAdvance.h index b783666..ef2ac40 100644 --- a/src/finer/PhaseAdvance.h +++ b/src/finer/PhaseAdvance.h @@ -55,6 +55,7 @@ public: m_currentPeaks = allocate_and_zero_channels(ch, m_blockSize); m_prevPeaks = allocate_and_zero_channels(ch, m_blockSize); m_greatestChannel = allocate_and_zero(m_blockSize); + m_prevInMag = allocate_and_zero_channels(ch, m_blockSize); m_prevInPhase = allocate_and_zero_channels(ch, m_blockSize); m_prevOutPhase = allocate_and_zero_channels(ch, m_blockSize); m_unlocked = allocate_and_zero_channels(ch, m_blockSize); @@ -65,6 +66,7 @@ public: deallocate_channels(m_currentPeaks, ch); deallocate_channels(m_prevPeaks, ch); deallocate(m_greatestChannel); + deallocate_channels(m_prevInMag, ch); deallocate_channels(m_prevInPhase, ch); deallocate_channels(m_prevOutPhase, ch); deallocate_channels(m_unlocked, ch); @@ -120,10 +122,33 @@ public: int startBin = binForFrequency(band.f0); int endBin = binForFrequency(band.f1); if (startBin > highest || endBin < lowest) continue; - int count = endBin - startBin; + int count = endBin - startBin + 1; m_peakPicker.findNearestAndNextPeaks(mag[c], startBin, count, - band.p, m_currentPeaks[c]); + band.p, m_currentPeaks[c], + nullptr); } + + m_peakPicker.findNearestAndNextPeaks(m_prevInMag[c], + lowest, highest - lowest + 1, + 2, m_prevPeaks[c], + nullptr); + +/* + static int counter = 0; + if (c == 0) { + if (++counter > 140 && counter < 150) { + std::cout << "Magnitudes and peaks (fftSize = " << m_parameters.fftSize << "):" << std::endl; + for (int i = 0; i < bs; ++i) { + if (m_currentPeaks[c][i] == i) { + std::cout << "*"; + } + std::cout << mag[c][i] << ", "; + } + std::cout << std::endl; + } + } +*/ + } if (channels > 1) { @@ -163,10 +188,9 @@ public: ++phaseLockBand; } double ph = 0.0; - /* if (inRange(f, g->phaseReset) || inRange(f, g->kick)) { ph = phase[c][i]; - } else */ if (inRange (f, g->highPercussive)) { + } else if (inRange (f, g->highPercussive)) { ph = m_unlocked[c][i]; } else { int peak = m_currentPeaks[c][i]; @@ -200,8 +224,9 @@ public: for (int i = lowest; i <= highest; ++i) { m_prevInPhase[c][i] = phase[c][i]; } - } - for (int c = 0; c < channels; ++c) { + for (int i = lowest; i <= highest; ++i) { + m_prevInMag[c][i] = mag[c][i]; + } for (int i = lowest; i <= highest; ++i) { m_prevOutPhase[c][i] = outPhase[c][i]; } @@ -209,11 +234,12 @@ public: //!!! NB in the original we use a different value of p for //!!! peak-picking the prior magnitudes - this isn't carried - //!!! over here + //!!! over here - it is now but I don't think this was the + //!!! full cause of our burbling - int **tmp = m_prevPeaks; - m_prevPeaks = m_currentPeaks; - m_currentPeaks = tmp; +// int **tmp = m_prevPeaks; +// m_prevPeaks = m_currentPeaks; +// m_currentPeaks = tmp; } protected: @@ -223,6 +249,7 @@ protected: int **m_currentPeaks; int **m_prevPeaks; int *m_greatestChannel; + float **m_prevInMag; float **m_prevInPhase; double **m_prevOutPhase; double **m_unlocked; diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index f903c82..a6f0fdc 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -187,7 +187,12 @@ R3StretcherImpl::consume() std::cout << "outhop = " << outhop << std::endl; - double instantaneousRatio = double(outhop) / double(m_inhop); + //!!! + outhop = int(round(m_inhop * ratio)); + + //!!! shouldn't this be the *previous* outhop? +// double instantaneousRatio = double(outhop) / double(m_inhop); + double instantaneousRatio = ratio; while (m_channelData.at(0)->outbuf->getWriteSpace() >= outhop) { From c4a78b4b55f754755ec7c2d6a99db6b6fa6b08eb Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 24 May 2022 15:39:28 +0100 Subject: [PATCH 018/184] Use doubles throughout (simpler, faster) --- src/common/RingBuffer.h | 17 ++++++++++------ src/finer/BinClassifier.h | 32 ++++++++++++++--------------- src/finer/BinSegmenter.h | 2 +- src/finer/Guide.h | 38 +++++++++++++++++------------------ src/finer/PhaseAdvance.h | 15 +++++++------- src/finer/R3StretcherImpl.cpp | 14 +++++-------- src/finer/R3StretcherImpl.h | 22 ++++++++++---------- 7 files changed, 71 insertions(+), 69 deletions(-) diff --git a/src/common/RingBuffer.h b/src/common/RingBuffer.h index fb80788..a905a33 100644 --- a/src/common/RingBuffer.h +++ b/src/common/RingBuffer.h @@ -138,8 +138,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 +389,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 +400,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 +408,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/finer/BinClassifier.h b/src/finer/BinClassifier.h index cf2655c..73febee 100644 --- a/src/finer/BinClassifier.h +++ b/src/finer/BinClassifier.h @@ -50,11 +50,11 @@ public: int verticalFilterLength; double harmonicThreshold; double percussiveThreshold; - float silenceThreshold; + double silenceThreshold; Parameters(int _binCount, int _horizontalFilterLength, int _horizontalFilterLag, int _verticalFilterLength, double _harmonicThreshold, double _percussiveThreshold, - float _silenceThreshold) : + double _silenceThreshold) : binCount(_binCount), horizontalFilterLength(_horizontalFilterLength), horizontalFilterLag(_horizontalFilterLag), @@ -66,21 +66,21 @@ public: BinClassifier(Parameters parameters) : m_parameters(parameters), - m_vFilter(new MovingMedian(m_parameters.verticalFilterLength)), + m_vFilter(new MovingMedian(m_parameters.verticalFilterLength)), m_vfQueue(parameters.horizontalFilterLag) { int n = m_parameters.binCount; for (int i = 0; i < n; ++i) { - m_hFilters.push_back(std::make_shared> + m_hFilters.push_back(std::make_shared> (m_parameters.horizontalFilterLength)); } - m_hf = allocate_and_zero(n); - m_vf = allocate_and_zero(n); + m_hf = allocate_and_zero(n); + m_vf = allocate_and_zero(n); for (int i = 0; i < m_parameters.horizontalFilterLag; ++i) { - float *entry = allocate_and_zero(n); + double *entry = allocate_and_zero(n); m_vfQueue.write(&entry, 1); } } @@ -88,7 +88,7 @@ public: ~BinClassifier() { while (m_vfQueue.getReadSpace() > 0) { - float *entry = m_vfQueue.readOne(); + double *entry = m_vfQueue.readOne(); deallocate(entry); } @@ -96,7 +96,7 @@ public: deallocate(m_vf); } - void classify(const float *const mag, Classification *classification) { + void classify(const double *const mag, Classification *classification) { const int n = m_parameters.binCount; for (int i = 0; i < n; ++i) { @@ -105,10 +105,10 @@ public: } v_copy(m_vf, mag, n); - MovingMedian::filter(*m_vFilter, m_vf); + MovingMedian::filter(*m_vFilter, m_vf); if (m_parameters.horizontalFilterLag > 0) { - float *lagged = m_vfQueue.readOne(); + double *lagged = m_vfQueue.readOne(); m_vfQueue.write(&m_vf, 1); m_vf = lagged; } @@ -134,13 +134,13 @@ public: protected: Parameters m_parameters; - std::vector>> m_hFilters; - std::unique_ptr> m_vFilter; + std::vector>> m_hFilters; + std::unique_ptr> m_vFilter; // We manage the queued frames through pointer swapping, hence // bare pointers here - float *m_hf; - float *m_vf; - RingBuffer m_vfQueue; + double *m_hf; + double *m_vf; + RingBuffer m_vfQueue; BinClassifier(const BinClassifier &) =delete; BinClassifier &operator=(const BinClassifier &) =delete; diff --git a/src/finer/BinSegmenter.h b/src/finer/BinSegmenter.h index e64ed39..f6636aa 100644 --- a/src/finer/BinSegmenter.h +++ b/src/finer/BinSegmenter.h @@ -62,7 +62,7 @@ public: { } - Segmentation segment(const float *const mag) { + Segmentation segment(const double *const mag) { int n = m_classifierParameters.binCount; m_classifier.classify(mag, m_classification.data()); for (int i = 0; i < n; ++i) { diff --git a/src/finer/Guide.h b/src/finer/Guide.h index 40313bc..de6f10e 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -35,9 +35,9 @@ class Guide public: struct FftBand { int fftSize; - float f0; - float f1; - FftBand(int _s, float _f0, float _f1) : + 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) { } @@ -45,10 +45,10 @@ public: struct PhaseLockBand { int p; - float beta; - float f0; - float f1; - PhaseLockBand(int _p, float _beta, float _f0, float _f1) : + 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) { } @@ -56,9 +56,9 @@ public: struct Range { bool present; - float f0; - float f1; - Range(bool _present, float _f0, float _f1) : + 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) { } @@ -76,9 +76,9 @@ public: struct BandLimits { int fftSize; - float f0min; - float f1max; - BandLimits(int _fftSize, float _f0min, float _f1max) : + double f0min; + double f1max; + BandLimits(int _fftSize, double _f0min, double _f1max) : fftSize(_fftSize), f0min(_f0min), f1max(_f1max) { } BandLimits() : fftSize(0), f0min(0.f), f1max(0.f) { } @@ -132,9 +132,9 @@ public: } void calculate(double ratio, - const float *const magnitudes, + const double *const magnitudes, const int *const troughs, - const float *const prevMagnitudes, + const double *const prevMagnitudes, const BinSegmenter::Segmentation &segmentation, const BinSegmenter::Segmentation &prevSegmentation, const BinSegmenter::Segmentation &nextSegmentation, @@ -272,17 +272,17 @@ protected: return value; } - bool checkPotentialKick(const float *const magnitudes, - const float *const prevMagnitudes) const { + bool checkPotentialKick(const double *const magnitudes, + const double *const prevMagnitudes) const { int b = binForFrequency(200.0); - float here = 0.0, there = 0.0; + 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-3f && here > there * 1.4f); + return (here > 10.e-3 && here > there * 1.4); } double snapToTrough(double f, const int *const troughs) const { diff --git a/src/finer/PhaseAdvance.h b/src/finer/PhaseAdvance.h index ef2ac40..147e089 100644 --- a/src/finer/PhaseAdvance.h +++ b/src/finer/PhaseAdvance.h @@ -55,8 +55,9 @@ public: m_currentPeaks = allocate_and_zero_channels(ch, m_blockSize); m_prevPeaks = allocate_and_zero_channels(ch, m_blockSize); m_greatestChannel = allocate_and_zero(m_blockSize); - m_prevInMag = allocate_and_zero_channels(ch, m_blockSize); - m_prevInPhase = allocate_and_zero_channels(ch, m_blockSize); + //!!! there is also a prevMag in R3StretcherImpl which could be passed in to here instead + m_prevInMag = allocate_and_zero_channels(ch, m_blockSize); + m_prevInPhase = allocate_and_zero_channels(ch, m_blockSize); m_prevOutPhase = allocate_and_zero_channels(ch, m_blockSize); m_unlocked = allocate_and_zero_channels(ch, m_blockSize); } @@ -73,8 +74,8 @@ public: } void advance(double *const *outPhase, - const float *const *mag, - const float *const *phase, + const double *const *mag, + const double *const *phase, const Guide::Configuration &configuration, const Guide::Guidance *const *guidance, int inhop, @@ -245,12 +246,12 @@ public: protected: Parameters m_parameters; int m_blockSize; - Peak m_peakPicker; + Peak m_peakPicker; int **m_currentPeaks; int **m_prevPeaks; int *m_greatestChannel; - float **m_prevInMag; - float **m_prevInPhase; + double **m_prevInMag; + double **m_prevInPhase; double **m_prevOutPhase; double **m_unlocked; bool m_reported; diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index a6f0fdc..c5397bf 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -240,7 +240,7 @@ R3StretcherImpl::consume() (scale->timeDomainFrame.data(), scale->mag.data(), scale->phase.data()); - v_scale(scale->mag.data(), 1.f / float(fftSize), + v_scale(scale->mag.data(), 1.0 / double(fftSize), scale->mag.size()); } @@ -291,10 +291,6 @@ R3StretcherImpl::consume() // copy to prevMag before filtering v_copy(scale->prevMag.data(), scale->mag.data(), bufSize); v_copy(scale->prevOutPhase.data(), scale->outPhase.data(), bufSize); - //!!! seems wasteful - for (int i = 0; i < bufSize; ++i) { - scale->phase[i] = princarg(scale->outPhase[i]); - } } for (const auto &band : cd->guidance.fftBands) { @@ -302,18 +298,18 @@ R3StretcherImpl::consume() auto scale = cd->scales.at(fftSize); auto scaleData = m_scaleData.at(fftSize); - //!!! messy and v slow, but leave it until we've + //!!! messy and slow, but leave it until we've //!!! discovered whether we need a window accumulator //!!! (we probably do) int analysisWindowSize = scaleData->analysisWindow.getSize(); int synthesisWindowSize = scaleData->synthesisWindow.getSize(); int offset = (analysisWindowSize - synthesisWindowSize) / 2; - float winscale = 0.f; + double winscale = 0.0; for (int i = 0; i < synthesisWindowSize; ++i) { winscale += scaleData->analysisWindow.getValue(i + offset) * scaleData->synthesisWindow.getValue(i); } - winscale = float(outhop) / winscale; + winscale = double(outhop) / winscale; double factor = m_parameters.sampleRate / double(fftSize); for (int i = 0; i < fftSize/2 + 1; ++i) { @@ -333,7 +329,7 @@ R3StretcherImpl::consume() auto scaleData = m_scaleData.at(fftSize); scaleData->fft.inversePolar(scale->mag.data(), - scale->phase.data(), + scale->outPhase.data(), scale->timeDomainFrame.data()); int synthesisWindowSize = scaleData->synthesisWindow.getSize(); diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 7e31432..d0bd650 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -127,14 +127,14 @@ protected: int fftSize; int bufSize; // size of every freq-domain array here: fftSize/2 + 1 //!!! review later which of these we are actually using! - FixedVector timeDomainFrame; - FixedVector mag; - FixedVector phase; + FixedVector timeDomainFrame; + FixedVector mag; + FixedVector phase; FixedVector outPhase; //!!! "advanced"? FixedVector nextTroughs; //!!! not used in every scale - FixedVector prevMag; //!!! not used in every scale + FixedVector prevMag; //!!! not used in every scale FixedVector prevOutPhase; - FixedVector accumulator; + FixedVector accumulator; ChannelScaleData(int _fftSize, int _longestFftSize) : fftSize(_fftSize), @@ -161,7 +161,7 @@ protected: BinSegmenter::Segmentation prevSegmentation; BinSegmenter::Segmentation nextSegmentation; Guide::Guidance guidance; - FixedVector mixdown; + FixedVector mixdown; std::unique_ptr> inbuf; std::unique_ptr> outbuf; ChannelData(BinSegmenter::Parameters segmenterParameters, @@ -179,8 +179,8 @@ protected: struct ChannelAssembly { // Vectors of bare pointers, used to package container data // from different channels into arguments for PhaseAdvance - FixedVector mag; - FixedVector phase; + FixedVector mag; + FixedVector phase; FixedVector guidance; FixedVector outPhase; ChannelAssembly(int channels) : @@ -190,8 +190,8 @@ protected: struct ScaleData { FFT fft; - Window analysisWindow; - Window synthesisWindow; + Window analysisWindow; + Window synthesisWindow; GuidedPhaseAdvance guided; ScaleData(GuidedPhaseAdvance::Parameters guidedParameters) : fft(guidedParameters.fftSize), @@ -210,7 +210,7 @@ protected: Guide m_guide; Guide::Configuration m_guideConfiguration; ChannelAssembly m_channelAssembly; - Peak> m_troughPicker; + Peak> m_troughPicker; std::unique_ptr m_calculator; int m_inhop; bool m_draining; From d45831fcc5cd6bed5325bf5310b1129d6cbff9f3 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 24 May 2022 16:54:05 +0100 Subject: [PATCH 019/184] Add the overlooked fftshift (that explains it!); carry out polar/cartesian conversion only for bins of interest --- src/finer/Guide.h | 27 +++++++++----- src/finer/PhaseAdvance.h | 6 +-- src/finer/R3StretcherImpl.cpp | 69 +++++++++++++++++++++++++---------- src/finer/R3StretcherImpl.h | 10 ++++- 4 files changed, 78 insertions(+), 34 deletions(-) diff --git a/src/finer/Guide.h b/src/finer/Guide.h index de6f10e..9c36f34 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -78,10 +78,14 @@ public: int fftSize; double f0min; double f1max; - BandLimits(int _fftSize, double _f0min, double _f1max) : - fftSize(_fftSize), f0min(_f0min), f1max(_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) { } + fftSize(0), f0min(0.f), f1max(0.f), b0min(0), b1max(0) { } }; struct Configuration { @@ -116,15 +120,18 @@ public: { double rate = m_parameters.sampleRate; double nyquist = rate / 2.0; + + int bandFftSize = roundUp(int(ceil(rate/16.0))); m_configuration.fftBandLimits[0] = - BandLimits(roundUp(int(ceil(rate/16.0))), - 0.0, m_maxLower); + BandLimits(bandFftSize, rate, 0.0, m_maxLower); + + bandFftSize = roundUp(int(ceil(rate/32.0))); m_configuration.fftBandLimits[1] = - BandLimits(roundUp(int(ceil(rate/32.0))), - m_minLower, m_maxHigher); + BandLimits(bandFftSize, rate, m_minLower, m_maxHigher); + + bandFftSize = roundUp(int(ceil(rate/64.0))); m_configuration.fftBandLimits[2] = - BandLimits(roundUp(int(ceil(rate/64.0))), - m_minHigher, rate/2.0); + BandLimits(bandFftSize, rate, m_minHigher, rate/2.0); } const Configuration &getConfiguration() const { @@ -225,6 +232,7 @@ public: guidance.phaseLockBands[3].f0 = higher; guidance.phaseLockBands[3].f1 = nyquist; + /* std::ostringstream str; str << "Guidance: FFT bands: [" << guidance.fftBands[0].fftSize << " from " @@ -240,6 +248,7 @@ public: << guidance.phaseReset.f0 << " to " << guidance.phaseReset.f1 << "]" << std::endl; m_parameters.logger(str.str()); + */ } protected: diff --git a/src/finer/PhaseAdvance.h b/src/finer/PhaseAdvance.h index 147e089..69e2df8 100644 --- a/src/finer/PhaseAdvance.h +++ b/src/finer/PhaseAdvance.h @@ -95,10 +95,8 @@ public: int channels = m_parameters.channels; double ratio = double(outhop) / double(inhop); - int lowest = binForFrequency - (configuration.fftBandLimits[myFftBand].f0min); - int highest = binForFrequency - (configuration.fftBandLimits[myFftBand].f1max); + int lowest = configuration.fftBandLimits[myFftBand].b0min; + int highest = configuration.fftBandLimits[myFftBand].b1max; if (!m_reported) { std::ostringstream ostr; diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index c5397bf..8f0826a 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -23,6 +23,8 @@ #include "R3StretcherImpl.h" +#include "common/VectorOpsComplex.h" + #include namespace RubberBand { @@ -60,6 +62,8 @@ R3StretcherImpl::calculateHop() m_inhop = int(round(inhop)); } + m_prevOuthop = int(round(m_inhop * ratio)); + std::ostringstream str; str << "R3StretcherImpl::calculateHop: for effective ratio " << ratio << " calculated (typical) inhop of " << m_inhop << std::endl; @@ -187,17 +191,11 @@ R3StretcherImpl::consume() std::cout << "outhop = " << outhop << std::endl; - //!!! - outhop = int(round(m_inhop * ratio)); - - //!!! shouldn't this be the *previous* outhop? -// double instantaneousRatio = double(outhop) / double(m_inhop); - double instantaneousRatio = ratio; + double instantaneousRatio = double(m_prevOuthop) / double(m_inhop); + m_prevOuthop = outhop; while (m_channelData.at(0)->outbuf->getWriteSpace() >= outhop) { -//!!! m_parameters.logger("consume looping"); - int readSpace = m_channelData.at(0)->inbuf->getReadSpace(); if (readSpace < longest) { if (m_draining) { @@ -213,7 +211,7 @@ R3StretcherImpl::consume() auto cd = m_channelData.at(c); auto longestScale = cd->scales.at(longest); - auto buf = longestScale->timeDomainFrame.data(); + auto buf = longestScale->timeDomain.data(); if (readSpace < longest) { v_zero(buf, longest); @@ -228,7 +226,7 @@ R3StretcherImpl::consume() if (fftSize == longest) continue; int offset = (longest - fftSize) / 2; m_scaleData.at(fftSize)->analysisWindow.cut - (buf + offset, scale->timeDomainFrame.data()); + (buf + offset, scale->timeDomain.data()); } m_scaleData.at(longest)->analysisWindow.cut(buf); @@ -236,10 +234,26 @@ R3StretcherImpl::consume() for (auto it: cd->scales) { int fftSize = it.first; auto scale = it.second; - m_scaleData.at(fftSize)->fft.forwardPolar - (scale->timeDomainFrame.data(), - scale->mag.data(), - scale->phase.data()); + + 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) { + int offset = b.b0min; + v_cartesian_to_polar + (scale->mag.data() + offset, + scale->phase.data() + offset, + scale->real.data() + offset, + scale->imag.data() + offset, + b.b1max - offset); + break; + } + } + v_scale(scale->mag.data(), 1.0 / double(fftSize), scale->mag.size()); } @@ -328,17 +342,34 @@ R3StretcherImpl::consume() auto scale = it.second; auto scaleData = m_scaleData.at(fftSize); - scaleData->fft.inversePolar(scale->mag.data(), - scale->outPhase.data(), - scale->timeDomainFrame.data()); - + for (const auto &b : m_guideConfiguration.fftBandLimits) { + if (b.fftSize == fftSize) { + int offset = b.b0min; + v_zero(scale->real.data(), fftSize/2 + 1); + v_zero(scale->imag.data(), fftSize/2 + 1); + v_polar_to_cartesian + (scale->real.data() + offset, + scale->imag.data() + offset, + scale->mag.data() + offset, + scale->outPhase.data() + offset, + b.b1max - offset); + break; + } + } + + scaleData->fft.inverse(scale->real.data(), + scale->imag.data(), + scale->timeDomain.data()); + + v_fftshift(scale->timeDomain.data(), fftSize); + int synthesisWindowSize = scaleData->synthesisWindow.getSize(); int fromOffset = (fftSize - synthesisWindowSize) / 2; int toOffset = (m_guideConfiguration.longestFftSize - synthesisWindowSize) / 2; scaleData->synthesisWindow.cutAndAdd - (scale->timeDomainFrame.data() + fromOffset, + (scale->timeDomain.data() + fromOffset, scale->accumulator.data() + toOffset); } diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index d0bd650..45ddd85 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -65,6 +65,7 @@ public: m_channelAssembly(m_parameters.channels), m_troughPicker(m_guideConfiguration.classificationFftSize / 2 + 1), m_inhop(1), + m_prevOuthop(1), m_draining(false) { BinSegmenter::Parameters segmenterParameters @@ -127,7 +128,9 @@ protected: int fftSize; int bufSize; // size of every freq-domain array here: fftSize/2 + 1 //!!! review later which of these we are actually using! - FixedVector timeDomainFrame; + FixedVector timeDomain; + FixedVector real; + FixedVector imag; FixedVector mag; FixedVector phase; FixedVector outPhase; //!!! "advanced"? @@ -139,7 +142,9 @@ protected: ChannelScaleData(int _fftSize, int _longestFftSize) : fftSize(_fftSize), bufSize(fftSize/2 + 1), - timeDomainFrame(fftSize, 0.f), + timeDomain(fftSize, 0.f), + real(bufSize, 0.f), + imag(bufSize, 0.f), mag(bufSize, 0.f), phase(bufSize, 0.f), outPhase(bufSize, 0.f), @@ -213,6 +218,7 @@ protected: Peak> m_troughPicker; std::unique_ptr m_calculator; int m_inhop; + int m_prevOuthop; bool m_draining; void consume(); From a9a0b4851ae4316773af97122274f510db754a1b Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 24 May 2022 17:35:14 +0100 Subject: [PATCH 020/184] Fix single-file build --- single/RubberBandSingle.cpp | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/single/RubberBandSingle.cpp b/single/RubberBandSingle.cpp index 422e8f5..a04b2c4 100644 --- a/single/RubberBandSingle.cpp +++ b/single/RubberBandSingle.cpp @@ -56,25 +56,24 @@ #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/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/StretcherImpl.cpp" +#include "../src/faster/StretcherProcess.cpp" +#include "../src/finer/R3StretcherImpl.cpp" +#include "../src/RubberBandStretcher.cpp" #include "../src/rubberband-c.cpp" From 9ed4be514442bfe7a02592791d5e9e58e05329db Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 24 May 2022 17:35:23 +0100 Subject: [PATCH 021/184] Fix some non-realtime-safe operations reported by Stoat --- src/faster/StretcherImpl.h | 2 +- src/finer/R3StretcherImpl.cpp | 61 ++++++++++++++++++++--------------- 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/src/faster/StretcherImpl.h b/src/faster/StretcherImpl.h index bcd0abd..98be6fc 100644 --- a/src/faster/StretcherImpl.h +++ b/src/faster/StretcherImpl.h @@ -24,7 +24,7 @@ #ifndef RUBBERBAND_STRETCHERIMPL_H #define RUBBERBAND_STRETCHERIMPL_H -#include "../rubberband/RubberBandStretcher.h" +#include "../../rubberband/RubberBandStretcher.h" #include "../common/Window.h" #include "../common/FFT.h" diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 8f0826a..2510f6c 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -23,7 +23,7 @@ #include "R3StretcherImpl.h" -#include "common/VectorOpsComplex.h" +#include "../common/VectorOpsComplex.h" #include @@ -112,6 +112,7 @@ R3StretcherImpl::getSamplesRequired() const } } +//!!! __attribute__((annotate("realtime"))) void R3StretcherImpl::process(const float *const *input, size_t samples, bool final) { @@ -144,6 +145,7 @@ R3StretcherImpl::process(const float *const *input, size_t samples, bool final) consume(); } +//!!! __attribute__((annotate("realtime"))) int R3StretcherImpl::available() const { @@ -153,6 +155,7 @@ R3StretcherImpl::available() const else return av; } +//!!! __attribute__((annotate("realtime"))) size_t R3StretcherImpl::retrieve(float *const *output, size_t samples) const { @@ -209,9 +212,15 @@ R3StretcherImpl::consume() for (int c = 0; c < m_parameters.channels; ++c) { - auto cd = m_channelData.at(c); - auto longestScale = cd->scales.at(longest); - auto buf = longestScale->timeDomain.data(); + // Our ChannelData, ScaleData, and ChannelScaleData maps + // contain shared_ptrs; whenever we put one in a variable + // in here we should use a reference, to avoid copying the + // shared_ptr (which is not realtime safe). Same goes for + // the map iterators. + + auto &cd = m_channelData.at(c); + auto &longestScale = cd->scales.at(longest); + double *buf = longestScale->timeDomain.data(); if (readSpace < longest) { v_zero(buf, longest); @@ -220,9 +229,9 @@ R3StretcherImpl::consume() cd->inbuf->peek(buf, longest); } - for (auto it: cd->scales) { + for (auto &it: cd->scales) { int fftSize = it.first; - auto scale = it.second; + auto &scale = it.second; if (fftSize == longest) continue; int offset = (longest - fftSize) / 2; m_scaleData.at(fftSize)->analysisWindow.cut @@ -231,9 +240,9 @@ R3StretcherImpl::consume() m_scaleData.at(longest)->analysisWindow.cut(buf); - for (auto it: cd->scales) { + for (auto &it: cd->scales) { int fftSize = it.first; - auto scale = it.second; + auto &scale = it.second; v_fftshift(scale->timeDomain.data(), fftSize); m_scaleData.at(fftSize)->fft.forward @@ -258,7 +267,7 @@ R3StretcherImpl::consume() scale->mag.size()); } - auto classifyScale = cd->scales.at(classify); + auto &classifyScale = cd->scales.at(classify); cd->prevSegmentation = cd->segmentation; cd->segmentation = cd->segmenter->segment(classifyScale->mag.data()); @@ -275,11 +284,11 @@ R3StretcherImpl::consume() cd->guidance); } - for (auto it : m_channelData[0]->scales) { + for (auto &it : m_channelData[0]->scales) { int fftSize = it.first; for (int c = 0; c < m_parameters.channels; ++c) { - auto cd = m_channelData.at(c); - auto classifyScale = cd->scales.at(fftSize); + auto &cd = m_channelData.at(c); + auto &classifyScale = cd->scales.at(fftSize); m_channelAssembly.mag[c] = classifyScale->mag.data(); m_channelAssembly.phase[c] = classifyScale->phase.data(); m_channelAssembly.guidance[c] = &cd->guidance; @@ -297,10 +306,10 @@ R3StretcherImpl::consume() for (int c = 0; c < m_parameters.channels; ++c) { - auto cd = m_channelData.at(c); + auto &cd = m_channelData.at(c); - for (auto it : cd->scales) { - auto scale = it.second; + for (auto &it : cd->scales) { + auto &scale = it.second; int bufSize = scale->bufSize; // copy to prevMag before filtering v_copy(scale->prevMag.data(), scale->mag.data(), bufSize); @@ -309,8 +318,8 @@ R3StretcherImpl::consume() for (const auto &band : cd->guidance.fftBands) { int fftSize = band.fftSize; - auto scale = cd->scales.at(fftSize); - auto scaleData = m_scaleData.at(fftSize); + auto &scale = cd->scales.at(fftSize); + auto &scaleData = m_scaleData.at(fftSize); //!!! messy and slow, but leave it until we've //!!! discovered whether we need a window accumulator @@ -337,10 +346,10 @@ R3StretcherImpl::consume() } } - for (auto it : cd->scales) { + for (auto &it : cd->scales) { int fftSize = it.first; - auto scale = it.second; - auto scaleData = m_scaleData.at(fftSize); + auto &scale = it.second; + auto &scaleData = m_scaleData.at(fftSize); for (const auto &b : m_guideConfiguration.fftBandLimits) { if (b.fftSize == fftSize) { @@ -373,20 +382,20 @@ R3StretcherImpl::consume() scale->accumulator.data() + toOffset); } - auto mixptr = cd->mixdown.data(); + double *mixptr = cd->mixdown.data(); v_zero(mixptr, outhop); - for (auto it : cd->scales) { - auto scale = it.second; + for (auto &it : cd->scales) { + auto &scale = it.second; v_add(mixptr, scale->accumulator.data(), outhop); } cd->outbuf->write(mixptr, outhop); - for (auto it : cd->scales) { + for (auto &it : cd->scales) { int fftSize = it.first; - auto scale = it.second; - auto accptr = scale->accumulator.data(); + auto &scale = it.second; + double *accptr = scale->accumulator.data(); int n = scale->accumulator.size() - outhop; v_move(accptr, accptr + outhop, n); From eb79336e933e93968a6b98602f093d890183f1e1 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 24 May 2022 17:49:50 +0100 Subject: [PATCH 022/184] Introduce a resampler (not used yet) --- src/common/Resampler.h | 3 +++ src/finer/R3StretcherImpl.h | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/common/Resampler.h b/src/common/Resampler.h index 142ce58..6080e0f 100644 --- a/src/common/Resampler.h +++ b/src/common/Resampler.h @@ -172,6 +172,9 @@ public: protected: Impl *d; int m_method; + + Resampler(const Resampler &) =delete; + Resampler &operator=(const Resampler &) =delete; }; } diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 45ddd85..699a996 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -30,6 +30,7 @@ #include "PhaseAdvance.h" #include "../common/StretchCalculator.h" +#include "../common/Resampler.h" #include "../common/FFT.h" #include "../common/FixedVector.h" #include "../common/Allocators.h" @@ -102,6 +103,15 @@ public: (new StretchCalculator(int(round(m_parameters.sampleRate)), //!!! which is a double... 1, false)); // no fixed inputIncrement + Resampler::Parameters resamplerParameters; + resamplerParameters.quality = Resampler::FastestTolerable; + resamplerParameters.dynamism = Resampler::RatioOftenChanging; + resamplerParameters.ratioChange = Resampler::SmoothRatioChange; + resamplerParameters.initialSampleRate = m_parameters.sampleRate; + resamplerParameters.maxBufferSize = m_guideConfiguration.longestFftSize; //!!!??? + m_resampler = std::unique_ptr + (new Resampler(resamplerParameters, m_parameters.channels)); + calculateHop(); } @@ -217,6 +227,7 @@ protected: ChannelAssembly m_channelAssembly; Peak> m_troughPicker; std::unique_ptr m_calculator; + std::unique_ptr m_resampler; int m_inhop; int m_prevOuthop; bool m_draining; From 680393c5c6d44dad6fe9a9a5c313829b0d4bcb1a Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 25 May 2022 09:43:08 +0100 Subject: [PATCH 023/184] Comments --- src/finer/R3StretcherImpl.cpp | 83 +++++++++++++++++++++++++++-------- 1 file changed, 65 insertions(+), 18 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 2510f6c..965bbc2 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -210,25 +210,30 @@ R3StretcherImpl::consume() } } + // Analysis. This is per channel + for (int c = 0; c < m_parameters.channels; ++c) { // Our ChannelData, ScaleData, and ChannelScaleData maps - // contain shared_ptrs; whenever we put one in a variable - // in here we should use a reference, to avoid copying the - // shared_ptr (which is not realtime safe). Same goes for - // the map iterators. + // contain shared_ptrs; whenever we retain one of them in + // a variable here, we do so by reference to avoid copying + // the shared_ptr (as that is not realtime safe). Same + // goes for the map iterators auto &cd = m_channelData.at(c); auto &longestScale = cd->scales.at(longest); double *buf = longestScale->timeDomain.data(); if (readSpace < longest) { - v_zero(buf, 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 for (auto &it: cd->scales) { int fftSize = it.first; auto &scale = it.second; @@ -238,28 +243,46 @@ R3StretcherImpl::consume() (buf + offset, scale->timeDomain.data()); } + // Then window the longest one m_scaleData.at(longest)->analysisWindow.cut(buf); + // FFT shift, forward FFT, and carry out cartesian-polar + // conversion for each FFT size for (auto &it: cd->scales) { int fftSize = it.first; auto &scale = it.second; v_fftshift(scale->timeDomain.data(), fftSize); - m_scaleData.at(fftSize)->fft.forward + + if (fftSize == m_guideConfiguration.classificationFftSize) { + + // For the classification scale we need the full range + m_scaleData.at(fftSize)->fft.forwardPolar + (scale->timeDomain.data(), + scale->mag.data(), + scale->phase.data()); + + } else { + + // For other scales we only need do + // cartesian-polar conversion for the necessary + // frequency subset + 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) { - int offset = b.b0min; - v_cartesian_to_polar - (scale->mag.data() + offset, - scale->phase.data() + offset, - scale->real.data() + offset, - scale->imag.data() + offset, - b.b1max - offset); - break; + for (const auto &b : m_guideConfiguration.fftBandLimits) { + if (b.fftSize == fftSize) { + int offset = b.b0min; + v_cartesian_to_polar + (scale->mag.data() + offset, + scale->phase.data() + offset, + scale->real.data() + offset, + scale->imag.data() + offset, + b.b1max - offset); + break; + } } } @@ -267,6 +290,9 @@ R3StretcherImpl::consume() scale->mag.size()); } + // Use the classification scale to get a bin segmentation + // and calculate the adaptive frequency guide for this + // channel auto &classifyScale = cd->scales.at(classify); cd->prevSegmentation = cd->segmentation; cd->segmentation = @@ -284,6 +310,8 @@ R3StretcherImpl::consume() cd->guidance); } + // Phase update. This is synchronised across all channels + for (auto &it : m_channelData[0]->scales) { int fftSize = it.first; for (int c = 0; c < m_parameters.channels; ++c) { @@ -304,6 +332,8 @@ R3StretcherImpl::consume() outhop); } + // Resynthesis. This is per channel + for (int c = 0; c < m_parameters.channels; ++c) { auto &cd = m_channelData.at(c); @@ -333,7 +363,11 @@ R3StretcherImpl::consume() scaleData->synthesisWindow.getValue(i); } winscale = double(outhop) / winscale; - + + // The frequency filter is applied naively in the + // frequency domain. Aliasing is reduced by the + // shorter resynthesis window + double factor = m_parameters.sampleRate / double(fftSize); for (int i = 0; i < fftSize/2 + 1; ++i) { double f = double(i) * factor; @@ -345,7 +379,11 @@ R3StretcherImpl::consume() } } } - + + // Resynthesise each FFT size (scale) individually, then + // sum. This is easier to manage scaling for in situations + // with a varying resynthesis hop + for (auto &it : cd->scales) { int fftSize = it.first; auto &scale = it.second; @@ -372,6 +410,13 @@ R3StretcherImpl::consume() v_fftshift(scale->timeDomain.data(), fftSize); + // Synthesis window is 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 = (m_guideConfiguration.longestFftSize - @@ -381,6 +426,8 @@ R3StretcherImpl::consume() (scale->timeDomain.data() + fromOffset, scale->accumulator.data() + toOffset); } + + // Mix and emit this channel double *mixptr = cd->mixdown.data(); v_zero(mixptr, outhop); From 973a334f75c5b9bfddc16533d5f30d5c1560543e Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 25 May 2022 11:14:19 +0100 Subject: [PATCH 024/184] Add readahead for segmenters --- src/finer/R3StretcherImpl.cpp | 154 +++++++++++++++++++++++----------- src/finer/R3StretcherImpl.h | 29 +++++-- 2 files changed, 129 insertions(+), 54 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 965bbc2..ee29a45 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -233,80 +233,134 @@ R3StretcherImpl::consume() // 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 + // 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; - auto &scale = it.second; - if (fftSize == longest) continue; + if (fftSize == classify || fftSize == longest) continue; int offset = (longest - fftSize) / 2; m_scaleData.at(fftSize)->analysisWindow.cut - (buf + offset, scale->timeDomain.data()); + (buf + offset, it.second->timeDomain.data()); } - // Then window the longest one + // The classification scale has a one-hop readahead (note + // that inhop is fixed), so populate its current data from + // the readahead and 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 + m_inhop, + readahead.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 + // 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 + + v_fftshift(readahead.timeDomain.data(), classify); + + v_copy(classifyScale->mag.data(), + readahead.mag.data(), + classifyScale->bufSize); + + v_copy(classifyScale->phase.data(), + readahead.phase.data(), + classifyScale->bufSize); + + 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) { + if (b.b0min > 0) { + v_cartesian_to_magnitudes(readahead.mag.data(), + classifyScale->real.data(), + classifyScale->imag.data(), + b.b0min); + } + + v_cartesian_to_polar(readahead.mag.data() + b.b0min, + readahead.phase.data() + b.b0min, + classifyScale->real.data() + b.b0min, + classifyScale->imag.data() + b.b0min, + b.b1max - b.b0min); + + if (b.b1max < classify/2 + 1) { + v_cartesian_to_magnitudes + (readahead.mag.data() + b.b1max, + classifyScale->real.data() + b.b1max, + classifyScale->imag.data() + b.b1max, + classify/2 + 1 - b.b1max); + } + + v_scale(classifyScale->mag.data(), + 1.0 / double(classify), + classifyScale->mag.size()); + break; + } + } + + // For the others 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) continue; auto &scale = it.second; - + v_fftshift(scale->timeDomain.data(), fftSize); - if (fftSize == m_guideConfiguration.classificationFftSize) { + m_scaleData.at(fftSize)->fft.forward(scale->timeDomain.data(), + scale->real.data(), + scale->imag.data()); - // For the classification scale we need the full range - m_scaleData.at(fftSize)->fft.forwardPolar - (scale->timeDomain.data(), - scale->mag.data(), - scale->phase.data()); - - } else { - - // For other scales we only need do - // cartesian-polar conversion for the necessary - // frequency subset - 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) { - int offset = b.b0min; - v_cartesian_to_polar - (scale->mag.data() + offset, - scale->phase.data() + offset, - scale->real.data() + offset, - scale->imag.data() + offset, - b.b1max - offset); - break; - } + //!!! This should be a map + for (const auto &b : m_guideConfiguration.fftBandLimits) { + if (b.fftSize == fftSize) { + v_cartesian_to_polar(scale->mag.data() + b.b0min, + scale->phase.data() + b.b0min, + scale->real.data() + b.b0min, + scale->imag.data() + b.b0min, + b.b1max - b.b0min); + v_scale(scale->mag.data() + b.b0min, + 1.0 / double(fftSize), + b.b1max - b.b0min); + break; } } - - v_scale(scale->mag.data(), 1.0 / double(fftSize), - scale->mag.size()); } // Use the classification scale to get a bin segmentation // and calculate the adaptive frequency guide for this // channel - auto &classifyScale = cd->scales.at(classify); cd->prevSegmentation = cd->segmentation; - cd->segmentation = - cd->segmenter->segment(classifyScale->mag.data()); + cd->segmentation = cd->nextSegmentation; + cd->nextSegmentation = cd->segmenter->segment(readahead.mag.data()); + m_troughPicker.findNearestAndNextPeaks (classifyScale->mag.data(), 3, nullptr, - classifyScale->nextTroughs.data()); + classifyScale->troughs.data()); + m_guide.calculate(instantaneousRatio, classifyScale->mag.data(), - classifyScale->nextTroughs.data(), + classifyScale->troughs.data(), classifyScale->prevMag.data(), cd->segmentation, cd->prevSegmentation, - BinSegmenter::Segmentation(), //!!! + cd->nextSegmentation, cd->guidance); } @@ -320,7 +374,7 @@ R3StretcherImpl::consume() m_channelAssembly.mag[c] = classifyScale->mag.data(); m_channelAssembly.phase[c] = classifyScale->phase.data(); m_channelAssembly.guidance[c] = &cd->guidance; - m_channelAssembly.outPhase[c] = classifyScale->outPhase.data(); + m_channelAssembly.outPhase[c] = classifyScale->advancedPhase.data(); } m_scaleData.at(fftSize)->guided.advance (m_channelAssembly.outPhase.data(), @@ -342,8 +396,12 @@ R3StretcherImpl::consume() auto &scale = it.second; int bufSize = scale->bufSize; // copy to prevMag before filtering - v_copy(scale->prevMag.data(), scale->mag.data(), bufSize); - v_copy(scale->prevOutPhase.data(), scale->outPhase.data(), bufSize); + v_copy(scale->prevMag.data(), + scale->mag.data(), + bufSize); + v_copy(scale->prevAdvancedPhase.data(), + scale->advancedPhase.data(), + bufSize); } for (const auto &band : cd->guidance.fftBands) { @@ -398,7 +456,7 @@ R3StretcherImpl::consume() (scale->real.data() + offset, scale->imag.data() + offset, scale->mag.data() + offset, - scale->outPhase.data() + offset, + scale->advancedPhase.data() + offset, b.b1max - offset); break; } diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 699a996..8e388b5 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -134,6 +134,21 @@ public: size_t getChannelCount() const; 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 @@ -143,10 +158,10 @@ protected: FixedVector imag; FixedVector mag; FixedVector phase; - FixedVector outPhase; //!!! "advanced"? - FixedVector nextTroughs; //!!! not used in every scale + FixedVector advancedPhase; + FixedVector troughs; //!!! not used in every scale FixedVector prevMag; //!!! not used in every scale - FixedVector prevOutPhase; + FixedVector prevAdvancedPhase; FixedVector accumulator; ChannelScaleData(int _fftSize, int _longestFftSize) : @@ -157,10 +172,10 @@ protected: imag(bufSize, 0.f), mag(bufSize, 0.f), phase(bufSize, 0.f), - outPhase(bufSize, 0.f), - nextTroughs(bufSize, 0), + advancedPhase(bufSize, 0.f), + troughs(bufSize, 0), prevMag(bufSize, 0.f), - prevOutPhase(bufSize, 0.f), + prevAdvancedPhase(bufSize, 0.f), accumulator(_longestFftSize, 0.f) { } @@ -171,6 +186,7 @@ protected: struct ChannelData { std::map> scales; + ClassificationReadaheadData readahead; std::unique_ptr segmenter; BinSegmenter::Segmentation segmentation; BinSegmenter::Segmentation prevSegmentation; @@ -183,6 +199,7 @@ protected: BinClassifier::Parameters classifierParameters, int ringBufferSize) : scales(), + readahead(segmenterParameters.fftSize), segmenter(new BinSegmenter(segmenterParameters, classifierParameters)), segmentation(), prevSegmentation(), nextSegmentation(), From 47476b90888e1c455a16072c800f8fac6c9f22e7 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 25 May 2022 11:16:06 +0100 Subject: [PATCH 025/184] Tidy --- src/finer/R3StretcherImpl.cpp | 3 --- src/finer/R3StretcherImpl.h | 7 ++----- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index ee29a45..47a6fa8 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -399,9 +399,6 @@ R3StretcherImpl::consume() v_copy(scale->prevMag.data(), scale->mag.data(), bufSize); - v_copy(scale->prevAdvancedPhase.data(), - scale->advancedPhase.data(), - bufSize); } for (const auto &band : cd->guidance.fftBands) { diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 8e388b5..1cc9d67 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -152,16 +152,14 @@ protected: struct ChannelScaleData { int fftSize; int bufSize; // size of every freq-domain array here: fftSize/2 + 1 - //!!! review later which of these we are actually using! FixedVector timeDomain; FixedVector real; FixedVector imag; FixedVector mag; FixedVector phase; FixedVector advancedPhase; - FixedVector troughs; //!!! not used in every scale - FixedVector prevMag; //!!! not used in every scale - FixedVector prevAdvancedPhase; + FixedVector troughs; + FixedVector prevMag; FixedVector accumulator; ChannelScaleData(int _fftSize, int _longestFftSize) : @@ -175,7 +173,6 @@ protected: advancedPhase(bufSize, 0.f), troughs(bufSize, 0), prevMag(bufSize, 0.f), - prevAdvancedPhase(bufSize, 0.f), accumulator(_longestFftSize, 0.f) { } From f5b381e086e025467f75888642369efb8378ce64 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 25 May 2022 11:26:16 +0100 Subject: [PATCH 026/184] Pull out per-channel analysis and resynthesis functions --- src/finer/R3StretcherImpl.cpp | 542 +++++++++++++++++----------------- src/finer/R3StretcherImpl.h | 2 + 2 files changed, 279 insertions(+), 265 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 47a6fa8..bae8894 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -179,9 +179,7 @@ void R3StretcherImpl::consume() { double ratio = getEffectiveRatio(); - int longest = m_guideConfiguration.longestFftSize; - int classify = m_guideConfiguration.classificationFftSize; m_calculator->setDebugLevel(3); @@ -194,11 +192,14 @@ R3StretcherImpl::consume() std::cout << "outhop = " << outhop << std::endl; - double instantaneousRatio = double(m_prevOuthop) / double(m_inhop); - m_prevOuthop = 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_draining) { @@ -210,158 +211,10 @@ R3StretcherImpl::consume() } } - // Analysis. This is per channel + // Analysis for (int c = 0; c < m_parameters.channels; ++c) { - - // Our ChannelData, ScaleData, and ChannelScaleData maps - // contain shared_ptrs; whenever we retain one of them in - // a variable here, we do so by reference to avoid copying - // the shared_ptr (as that is not realtime safe). Same - // goes for the map iterators - - auto &cd = m_channelData.at(c); - auto &longestScale = cd->scales.at(longest); - double *buf = longestScale->timeDomain.data(); - - 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 (note - // that inhop is fixed), so populate its current data from - // the readahead and 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 + m_inhop, - readahead.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 - - v_fftshift(readahead.timeDomain.data(), classify); - - v_copy(classifyScale->mag.data(), - readahead.mag.data(), - classifyScale->bufSize); - - v_copy(classifyScale->phase.data(), - readahead.phase.data(), - classifyScale->bufSize); - - 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) { - if (b.b0min > 0) { - v_cartesian_to_magnitudes(readahead.mag.data(), - classifyScale->real.data(), - classifyScale->imag.data(), - b.b0min); - } - - v_cartesian_to_polar(readahead.mag.data() + b.b0min, - readahead.phase.data() + b.b0min, - classifyScale->real.data() + b.b0min, - classifyScale->imag.data() + b.b0min, - b.b1max - b.b0min); - - if (b.b1max < classify/2 + 1) { - v_cartesian_to_magnitudes - (readahead.mag.data() + b.b1max, - classifyScale->real.data() + b.b1max, - classifyScale->imag.data() + b.b1max, - classify/2 + 1 - b.b1max); - } - - v_scale(classifyScale->mag.data(), - 1.0 / double(classify), - classifyScale->mag.size()); - break; - } - } - - // For the others 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) 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()); - - //!!! This should be a map - for (const auto &b : m_guideConfiguration.fftBandLimits) { - if (b.fftSize == fftSize) { - v_cartesian_to_polar(scale->mag.data() + b.b0min, - scale->phase.data() + b.b0min, - scale->real.data() + b.b0min, - scale->imag.data() + b.b0min, - b.b1max - b.b0min); - v_scale(scale->mag.data() + b.b0min, - 1.0 / double(fftSize), - b.b1max - b.b0min); - break; - } - } - } - - // Use the classification scale to get a bin segmentation - // and calculate the adaptive frequency guide for this - // channel - cd->prevSegmentation = cd->segmentation; - cd->segmentation = cd->nextSegmentation; - cd->nextSegmentation = cd->segmenter->segment(readahead.mag.data()); - - m_troughPicker.findNearestAndNextPeaks - (classifyScale->mag.data(), 3, nullptr, - classifyScale->troughs.data()); - - m_guide.calculate(instantaneousRatio, - classifyScale->mag.data(), - classifyScale->troughs.data(), - classifyScale->prevMag.data(), - cd->segmentation, - cd->prevSegmentation, - cd->nextSegmentation, - cd->guidance); + analyseChannel(c, m_prevOuthop); } // Phase update. This is synchronised across all channels @@ -370,11 +223,11 @@ R3StretcherImpl::consume() int fftSize = it.first; for (int c = 0; c < m_parameters.channels; ++c) { auto &cd = m_channelData.at(c); - auto &classifyScale = cd->scales.at(fftSize); - m_channelAssembly.mag[c] = classifyScale->mag.data(); - m_channelAssembly.phase[c] = classifyScale->phase.data(); + auto &scale = cd->scales.at(fftSize); + m_channelAssembly.mag[c] = scale->mag.data(); + m_channelAssembly.phase[c] = scale->phase.data(); m_channelAssembly.guidance[c] = &cd->guidance; - m_channelAssembly.outPhase[c] = classifyScale->advancedPhase.data(); + m_channelAssembly.outPhase[c] = scale->advancedPhase.data(); } m_scaleData.at(fftSize)->guided.advance (m_channelAssembly.outPhase.data(), @@ -386,134 +239,293 @@ R3StretcherImpl::consume() outhop); } - // Resynthesis. This is per channel + // Resynthesis for (int c = 0; c < m_parameters.channels; ++c) { + synthesiseChannel(c, outhop); + } + } - auto &cd = m_channelData.at(c); + m_prevOuthop = outhop; +} - for (auto &it : cd->scales) { - auto &scale = it.second; - int bufSize = scale->bufSize; - // copy to prevMag before filtering - v_copy(scale->prevMag.data(), - scale->mag.data(), - bufSize); - } - - for (const auto &band : cd->guidance.fftBands) { - int fftSize = band.fftSize; - auto &scale = cd->scales.at(fftSize); - auto &scaleData = m_scaleData.at(fftSize); +void +R3StretcherImpl::analyseChannel(int c, int prevOuthop) +{ + int longest = m_guideConfiguration.longestFftSize; + int classify = m_guideConfiguration.classificationFftSize; - //!!! messy and slow, but leave it until we've - //!!! discovered whether we need a window accumulator - //!!! (we probably do) - int analysisWindowSize = scaleData->analysisWindow.getSize(); - int synthesisWindowSize = scaleData->synthesisWindow.getSize(); - int offset = (analysisWindowSize - synthesisWindowSize) / 2; - double winscale = 0.0; - for (int i = 0; i < synthesisWindowSize; ++i) { - winscale += scaleData->analysisWindow.getValue(i + offset) * - scaleData->synthesisWindow.getValue(i); - } - winscale = double(outhop) / winscale; + auto &cd = m_channelData.at(c); + double *buf = cd->scales.at(longest)->timeDomain.data(); - // The frequency filter is applied naively in the - // frequency domain. Aliasing is reduced by the - // shorter resynthesis window - - double factor = m_parameters.sampleRate / double(fftSize); - for (int i = 0; i < fftSize/2 + 1; ++i) { - double f = double(i) * factor; - if (f >= band.f0 && f < band.f1) { - //!!! check the mod 2 bit from stretch-fn - scale->mag[i] *= winscale; - } else { - scale->mag[i] = 0.f; - } - } - } + 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) - // Resynthesise each FFT size (scale) individually, then - // sum. This is easier to manage scaling for in situations - // with a varying resynthesis hop + 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 (note + // that inhop is fixed), so populate its current data from + // the readahead and 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 + m_inhop, + readahead.timeDomain.data()); - for (auto &it : cd->scales) { - int fftSize = it.first; - auto &scale = it.second; - auto &scaleData = m_scaleData.at(fftSize); - - for (const auto &b : m_guideConfiguration.fftBandLimits) { - if (b.fftSize == fftSize) { - int offset = b.b0min; - v_zero(scale->real.data(), fftSize/2 + 1); - v_zero(scale->imag.data(), fftSize/2 + 1); - v_polar_to_cartesian - (scale->real.data() + offset, - scale->imag.data() + offset, - scale->mag.data() + offset, - scale->advancedPhase.data() + offset, - b.b1max - offset); - break; - } - } + // Finally window the longest scale + m_scaleData.at(longest)->analysisWindow.cut(buf); - scaleData->fft.inverse(scale->real.data(), - scale->imag.data(), - scale->timeDomain.data()); + // FFT shift, forward FFT, and carry out cartesian-polar + // conversion for each FFT size. - v_fftshift(scale->timeDomain.data(), fftSize); + // 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 - // Synthesis window is 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 = (m_guideConfiguration.longestFftSize - - synthesisWindowSize) / 2; + v_fftshift(readahead.timeDomain.data(), classify); - scaleData->synthesisWindow.cutAndAdd - (scale->timeDomain.data() + fromOffset, - scale->accumulator.data() + toOffset); - } - - // Mix and emit this channel + v_copy(classifyScale->mag.data(), + readahead.mag.data(), + classifyScale->bufSize); - double *mixptr = cd->mixdown.data(); - v_zero(mixptr, outhop); + v_copy(classifyScale->phase.data(), + readahead.phase.data(), + classifyScale->bufSize); - for (auto &it : cd->scales) { - auto &scale = it.second; - v_add(mixptr, scale->accumulator.data(), outhop); + 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) { + if (b.b0min > 0) { + v_cartesian_to_magnitudes(readahead.mag.data(), + classifyScale->real.data(), + classifyScale->imag.data(), + b.b0min); } - - cd->outbuf->write(mixptr, outhop); - - for (auto &it : cd->scales) { - int fftSize = it.first; - auto &scale = it.second; - double *accptr = scale->accumulator.data(); - - int n = scale->accumulator.size() - outhop; - v_move(accptr, accptr + outhop, n); - v_zero(accptr + n, outhop); + + v_cartesian_to_polar(readahead.mag.data() + b.b0min, + readahead.phase.data() + b.b0min, + classifyScale->real.data() + b.b0min, + classifyScale->imag.data() + b.b0min, + b.b1max - b.b0min); + + if (b.b1max < classify/2 + 1) { + v_cartesian_to_magnitudes + (readahead.mag.data() + b.b1max, + classifyScale->real.data() + b.b1max, + classifyScale->imag.data() + b.b1max, + classify/2 + 1 - b.b1max); } + + v_scale(classifyScale->mag.data(), + 1.0 / double(classify), + classifyScale->mag.size()); + break; + } + } + + // For the others we operate directly in the scale data + // and restrict the range for cartesian-polar conversion - if (readSpace < m_inhop) { - // This should happen only when draining - cd->inbuf->skip(readSpace); - } else { - cd->inbuf->skip(m_inhop); + for (auto &it: cd->scales) { + int fftSize = it.first; + if (fftSize == classify) 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()); + + //!!! This should be a map + for (const auto &b : m_guideConfiguration.fftBandLimits) { + if (b.fftSize == fftSize) { + v_cartesian_to_polar(scale->mag.data() + b.b0min, + scale->phase.data() + b.b0min, + scale->real.data() + b.b0min, + scale->imag.data() + b.b0min, + b.b1max - b.b0min); + v_scale(scale->mag.data() + b.b0min, + 1.0 / double(fftSize), + b.b1max - b.b0min); + break; } } } + + // Use the classification scale to get a bin segmentation + // and calculate the adaptive frequency guide for this + // channel + cd->prevSegmentation = cd->segmentation; + cd->segmentation = cd->nextSegmentation; + cd->nextSegmentation = cd->segmenter->segment(readahead.mag.data()); + + m_troughPicker.findNearestAndNextPeaks + (classifyScale->mag.data(), 3, nullptr, + classifyScale->troughs.data()); + + double instantaneousRatio = double(prevOuthop) / double(m_inhop); + m_guide.calculate(instantaneousRatio, + classifyScale->mag.data(), + classifyScale->troughs.data(), + classifyScale->prevMag.data(), + cd->segmentation, + cd->prevSegmentation, + cd->nextSegmentation, + cd->guidance); } +void +R3StretcherImpl::synthesiseChannel(int c, int outhop) +{ + int longest = m_guideConfiguration.longestFftSize; + + auto &cd = m_channelData.at(c); + + for (auto &it : cd->scales) { + auto &scale = it.second; + int bufSize = scale->bufSize; + // copy to prevMag before filtering + v_copy(scale->prevMag.data(), + scale->mag.data(), + bufSize); + } + + for (const auto &band : cd->guidance.fftBands) { + int fftSize = band.fftSize; + auto &scale = cd->scales.at(fftSize); + auto &scaleData = m_scaleData.at(fftSize); + + //!!! messy and slow, but leave it until we've + //!!! discovered whether we need a window accumulator + //!!! (we probably do) + int analysisWindowSize = scaleData->analysisWindow.getSize(); + int synthesisWindowSize = scaleData->synthesisWindow.getSize(); + int offset = (analysisWindowSize - synthesisWindowSize) / 2; + double winscale = 0.0; + for (int i = 0; i < synthesisWindowSize; ++i) { + winscale += scaleData->analysisWindow.getValue(i + offset) * + scaleData->synthesisWindow.getValue(i); + } + winscale = double(outhop) / winscale; + + // The frequency filter is applied naively in the + // frequency domain. Aliasing is reduced by the + // shorter resynthesis window + + double factor = m_parameters.sampleRate / double(fftSize); + for (int i = 0; i < fftSize/2 + 1; ++i) { + double f = double(i) * factor; + if (f >= band.f0 && f < band.f1) { + //!!! check the mod 2 bit from stretch-fn + scale->mag[i] *= winscale; + } else { + scale->mag[i] = 0.f; + } + } + } + + // Resynthesise each FFT size (scale) individually, then + // sum. This is easier to manage scaling for in situations + // with a varying resynthesis hop + + for (auto &it : cd->scales) { + int fftSize = it.first; + auto &scale = it.second; + auto &scaleData = m_scaleData.at(fftSize); + + for (const auto &b : m_guideConfiguration.fftBandLimits) { + if (b.fftSize == fftSize) { + int offset = b.b0min; + v_zero(scale->real.data(), fftSize/2 + 1); + v_zero(scale->imag.data(), fftSize/2 + 1); + v_polar_to_cartesian + (scale->real.data() + offset, + scale->imag.data() + offset, + scale->mag.data() + offset, + scale->advancedPhase.data() + offset, + b.b1max - offset); + break; + } + } + + scaleData->fft.inverse(scale->real.data(), + scale->imag.data(), + scale->timeDomain.data()); + + v_fftshift(scale->timeDomain.data(), fftSize); + + // Synthesis window is 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 and emit this channel + + double *mixptr = cd->mixdown.data(); + v_zero(mixptr, outhop); + + for (auto &it : cd->scales) { + auto &scale = it.second; + v_add(mixptr, scale->accumulator.data(), outhop); + } + + cd->outbuf->write(mixptr, outhop); + + for (auto &it : cd->scales) { + int fftSize = it.first; + auto &scale = it.second; + double *accptr = scale->accumulator.data(); + + int n = scale->accumulator.size() - outhop; + v_move(accptr, accptr + outhop, n); + v_zero(accptr + n, outhop); + } + + int readSpace = cd->inbuf->getReadSpace(); + if (readSpace < m_inhop) { + // This should happen only when draining + cd->inbuf->skip(readSpace); + } else { + cd->inbuf->skip(m_inhop); + } +} } diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 1cc9d67..9cd349e 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -248,6 +248,8 @@ protected: void consume(); void calculateHop(); + void analyseChannel(int channel, int prevOuthop); + void synthesiseChannel(int channel, int outhop); double getEffectiveRatio() const { return m_timeRatio * m_pitchScale; From 54515122b2386623e0053a6e61fb9e6f9d52a555 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 25 May 2022 13:47:40 +0100 Subject: [PATCH 027/184] Implement resampling --- src/finer/R3StretcherImpl.cpp | 129 ++++++++++++++++++++++++++++------ src/finer/R3StretcherImpl.h | 78 ++++---------------- 2 files changed, 123 insertions(+), 84 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index bae8894..5394dd8 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -29,6 +29,69 @@ namespace RubberBand { +R3StretcherImpl::R3StretcherImpl(Parameters parameters, + double initialTimeRatio, + double initialPitchScale) : + m_parameters(parameters), + m_timeRatio(initialTimeRatio), + m_pitchScale(initialPitchScale), + m_guide(Guide::Parameters(m_parameters.sampleRate, parameters.logger)), + m_guideConfiguration(m_guide.getConfiguration()), + m_channelAssembly(m_parameters.channels), + m_troughPicker(m_guideConfiguration.classificationFftSize / 2 + 1), + m_inhop(1), + m_prevOuthop(1), + m_draining(false) +{ + BinSegmenter::Parameters segmenterParameters + (m_guideConfiguration.classificationFftSize, + m_parameters.sampleRate); + BinClassifier::Parameters classifierParameters + (m_guideConfiguration.classificationFftSize / 2 + 1, + 9, 1, 10, 2.0, 2.0, 1.0e-7); + + 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_parameters.logger); + m_scaleData[fftSize] = std::make_shared(guidedParameters); + } + + m_calculator = std::unique_ptr + (new StretchCalculator(int(round(m_parameters.sampleRate)), //!!! which is a double... + 1, false)); // no fixed inputIncrement + + Resampler::Parameters resamplerParameters; + resamplerParameters.quality = Resampler::FastestTolerable; + resamplerParameters.dynamism = Resampler::RatioOftenChanging; + resamplerParameters.ratioChange = Resampler::SmoothRatioChange; + resamplerParameters.initialSampleRate = m_parameters.sampleRate; + resamplerParameters.maxBufferSize = m_guideConfiguration.longestFftSize; //!!!??? + m_resampler = std::unique_ptr + (new Resampler(resamplerParameters, m_parameters.channels)); + + calculateHop(); +} + void R3StretcherImpl::setTimeRatio(double ratio) { @@ -180,6 +243,7 @@ R3StretcherImpl::consume() { double ratio = getEffectiveRatio(); int longest = m_guideConfiguration.longestFftSize; + int channels = m_parameters.channels; m_calculator->setDebugLevel(3); @@ -213,7 +277,7 @@ R3StretcherImpl::consume() // Analysis - for (int c = 0; c < m_parameters.channels; ++c) { + for (int c = 0; c < channels; ++c) { analyseChannel(c, m_prevOuthop); } @@ -221,7 +285,7 @@ R3StretcherImpl::consume() for (auto &it : m_channelData[0]->scales) { int fftSize = it.first; - for (int c = 0; c < m_parameters.channels; ++c) { + 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(); @@ -241,9 +305,46 @@ R3StretcherImpl::consume() // Resynthesis - for (int c = 0; c < m_parameters.channels; ++c) { + for (int c = 0; c < channels; ++c) { synthesiseChannel(c, outhop); } + + // 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(), + outhop, + 1.0 / m_pitchScale, + m_draining && readSpace < longest); + } + + // Emit + + for (int c = 0; c < channels; ++c) { + auto &cd = m_channelData.at(c); + if (m_resampler) { + cd->outbuf->write(cd->resampled.data(), resampledCount); + } else { + cd->outbuf->write(cd->mixdown.data(), outhop); + } + + int readSpace = cd->inbuf->getReadSpace(); + if (readSpace < m_inhop) { + // This should happen only when draining + cd->inbuf->skip(readSpace); + } else { + cd->inbuf->skip(m_inhop); + } + } } m_prevOuthop = outhop; @@ -496,35 +597,23 @@ R3StretcherImpl::synthesiseChannel(int c, int outhop) scale->accumulator.data() + toOffset); } - // Mix and emit this channel + // Mix this channel and move the accumulator along - double *mixptr = cd->mixdown.data(); + float *mixptr = cd->mixdown.data(); v_zero(mixptr, outhop); for (auto &it : cd->scales) { auto &scale = it.second; - v_add(mixptr, scale->accumulator.data(), outhop); - } - cd->outbuf->write(mixptr, outhop); - - for (auto &it : cd->scales) { - int fftSize = it.first; - 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); } - - int readSpace = cd->inbuf->getReadSpace(); - if (readSpace < m_inhop) { - // This should happen only when draining - cd->inbuf->skip(readSpace); - } else { - cd->inbuf->skip(m_inhop); - } } } diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 9cd349e..e8bea99 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -57,64 +57,7 @@ public: R3StretcherImpl(Parameters parameters, double initialTimeRatio, - double initialPitchScale) : - m_parameters(parameters), - m_timeRatio(initialTimeRatio), - m_pitchScale(initialPitchScale), - m_guide(Guide::Parameters(m_parameters.sampleRate, parameters.logger)), - m_guideConfiguration(m_guide.getConfiguration()), - m_channelAssembly(m_parameters.channels), - m_troughPicker(m_guideConfiguration.classificationFftSize / 2 + 1), - m_inhop(1), - m_prevOuthop(1), - m_draining(false) - { - BinSegmenter::Parameters segmenterParameters - (m_guideConfiguration.classificationFftSize, - m_parameters.sampleRate); - BinClassifier::Parameters classifierParameters - (m_guideConfiguration.classificationFftSize / 2 + 1, - 9, 1, 10, 2.0, 2.0, 1.0e-7); - - int ringBufferSize = m_guideConfiguration.longestFftSize * 2; - - for (int c = 0; c < m_parameters.channels; ++c) { - m_channelData.push_back(std::make_shared - (segmenterParameters, - classifierParameters, - ringBufferSize)); - 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_parameters.logger); - m_scaleData[fftSize] = std::make_shared(guidedParameters); - } - - m_calculator = std::unique_ptr - (new StretchCalculator(int(round(m_parameters.sampleRate)), //!!! which is a double... - 1, false)); // no fixed inputIncrement - - Resampler::Parameters resamplerParameters; - resamplerParameters.quality = Resampler::FastestTolerable; - resamplerParameters.dynamism = Resampler::RatioOftenChanging; - resamplerParameters.ratioChange = Resampler::SmoothRatioChange; - resamplerParameters.initialSampleRate = m_parameters.sampleRate; - resamplerParameters.maxBufferSize = m_guideConfiguration.longestFftSize; //!!!??? - m_resampler = std::unique_ptr - (new Resampler(resamplerParameters, m_parameters.channels)); - - calculateHop(); - } - + double initialPitchScale); ~R3StretcherImpl() { } void reset(); @@ -189,20 +132,24 @@ protected: BinSegmenter::Segmentation prevSegmentation; BinSegmenter::Segmentation nextSegmentation; Guide::Guidance guidance; - FixedVector mixdown; + FixedVector mixdown; + FixedVector resampled; std::unique_ptr> inbuf; std::unique_ptr> outbuf; ChannelData(BinSegmenter::Parameters segmenterParameters, BinClassifier::Parameters classifierParameters, - int ringBufferSize) : + int longestFftSize, + int inRingBufferSize, + int outRingBufferSize) : scales(), readahead(segmenterParameters.fftSize), segmenter(new BinSegmenter(segmenterParameters, classifierParameters)), segmentation(), prevSegmentation(), nextSegmentation(), - mixdown(ringBufferSize, 0.f), //!!! could be shorter (bound is the max fft size I think) - inbuf(new RingBuffer(ringBufferSize)), - outbuf(new RingBuffer(ringBufferSize)) { } + mixdown(longestFftSize, 0.f), // though it could be shorter + resampled(outRingBufferSize, 0.f), + inbuf(new RingBuffer(inRingBufferSize)), + outbuf(new RingBuffer(outRingBufferSize)) { } }; struct ChannelAssembly { @@ -212,9 +159,12 @@ protected: FixedVector phase; FixedVector guidance; FixedVector outPhase; + FixedVector mixdown; + FixedVector resampled; ChannelAssembly(int channels) : mag(channels, nullptr), phase(channels, nullptr), - guidance(channels, nullptr), outPhase(channels, nullptr) { } + guidance(channels, nullptr), outPhase(channels, nullptr), + mixdown(channels, nullptr), resampled(channels, nullptr) { } }; struct ScaleData { From eb017476ca7e023efea15e79c75a5ff6a7ec290a Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 25 May 2022 13:51:23 +0100 Subject: [PATCH 028/184] Avoid resetting m_prevOuthop on every set-ratio call (client may call this very repeatedly) --- src/finer/R3StretcherImpl.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 5394dd8..6f888a1 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -90,6 +90,8 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, (new Resampler(resamplerParameters, m_parameters.channels)); calculateHop(); + + m_prevOuthop = int(round(m_inhop * getEffectiveRatio())); } void @@ -124,13 +126,6 @@ R3StretcherImpl::calculateHop() double inhop = std::min(proposedOuthop / ratio, 340.0); m_inhop = int(round(inhop)); } - - m_prevOuthop = int(round(m_inhop * ratio)); - - std::ostringstream str; - str << "R3StretcherImpl::calculateHop: for effective ratio " << ratio - << " calculated (typical) inhop of " << m_inhop << std::endl; - m_parameters.logger(str.str()); } double From 7d91fdb1b61d128187954e6601d2cc5f90f64da0 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 25 May 2022 13:54:11 +0100 Subject: [PATCH 029/184] Logging to cerr from a potentially RT context is bad even as a gross default just because it's so slow. I know we do it from R2 but I don't like it --- src/finer/R3StretcherImpl.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index e8bea99..bc70bff 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -51,7 +51,7 @@ public: int channels; std::function logger; Parameters(double _sampleRate, int _channels, - std::function _log = &logCerr) : + std::function _log = &logCout) : sampleRate(_sampleRate), channels(_channels), logger(_log) { } }; @@ -205,8 +205,8 @@ protected: return m_timeRatio * m_pitchScale; } - static void logCerr(const std::string &message) { - std::cerr << "RubberBandStretcher: " << message << std::endl; + static void logCout(const std::string &message) { + std::cout << "RubberBandStretcher: " << message << std::endl; } }; From 630a790ef8d0da9cfb405b3ea3a82bb2875671c9 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 25 May 2022 14:10:41 +0100 Subject: [PATCH 030/184] Use atomics for inhop and ratio, and read inhop once at start of consume in case it changes within --- src/finer/R3StretcherImpl.cpp | 51 +++++++++++++++++++++-------------- src/finer/R3StretcherImpl.h | 8 +++--- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 6f888a1..d637064 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -92,6 +92,13 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, calculateHop(); m_prevOuthop = int(round(m_inhop * getEffectiveRatio())); + + if (!m_inhop.is_lock_free()) { + m_parameters.logger("WARNING: std::atomic is not lock-free"); + } + if (!m_timeRatio.is_lock_free()) { + m_parameters.logger("WARNING: std::atomic is not lock-free"); + } } void @@ -113,19 +120,19 @@ R3StretcherImpl::calculateHop() { double ratio = getEffectiveRatio(); double proposedOuthop = 256; + double inhop = 1.0; if (ratio > 1.0) { - double inhop = proposedOuthop / ratio; + inhop = proposedOuthop / ratio; if (inhop < 1.0) { m_parameters.logger("WARNING: Extreme ratio yields ideal inhop < 1, results may be suspect"); - m_inhop = 1; - } else { - m_inhop = int(round(inhop)); + inhop = 1.0; } } else { - double inhop = std::min(proposedOuthop / ratio, 340.0); - m_inhop = int(round(inhop)); + inhop = std::min(proposedOuthop / ratio, 340.0); } + + m_inhop = int(round(inhop)); } double @@ -237,19 +244,21 @@ void R3StretcherImpl::consume() { double ratio = getEffectiveRatio(); + int inhop = m_inhop; + int longest = m_guideConfiguration.longestFftSize; int channels = m_parameters.channels; - m_calculator->setDebugLevel(3); +// m_calculator->setDebugLevel(3); int outhop = m_calculator->calculateSingle(ratio, 1.0 / m_pitchScale, 1.f, - m_inhop, + inhop, longest, longest); - std::cout << "outhop = " << outhop << std::endl; +// std::cout << "outhop = " << outhop << std::endl; while (m_channelData.at(0)->outbuf->getWriteSpace() >= outhop) { @@ -273,7 +282,7 @@ R3StretcherImpl::consume() // Analysis for (int c = 0; c < channels; ++c) { - analyseChannel(c, m_prevOuthop); + analyseChannel(c, inhop, m_prevOuthop); } // Phase update. This is synchronised across all channels @@ -294,7 +303,7 @@ R3StretcherImpl::consume() m_channelAssembly.phase.data(), m_guideConfiguration, m_channelAssembly.guidance.data(), - m_inhop, + inhop, outhop); } @@ -333,11 +342,11 @@ R3StretcherImpl::consume() } int readSpace = cd->inbuf->getReadSpace(); - if (readSpace < m_inhop) { + if (readSpace < inhop) { // This should happen only when draining cd->inbuf->skip(readSpace); } else { - cd->inbuf->skip(m_inhop); + cd->inbuf->skip(inhop); } } } @@ -346,7 +355,7 @@ R3StretcherImpl::consume() } void -R3StretcherImpl::analyseChannel(int c, int prevOuthop) +R3StretcherImpl::analyseChannel(int c, int inhop, int prevOuthop) { int longest = m_guideConfiguration.longestFftSize; int classify = m_guideConfiguration.classificationFftSize; @@ -377,16 +386,18 @@ R3StretcherImpl::analyseChannel(int c, int prevOuthop) (buf + offset, it.second->timeDomain.data()); } - // The classification scale has a one-hop readahead (note - // that inhop is fixed), so populate its current data from - // the readahead and the readahead from further down the - // long unwindowed frame. + // The classification scale has a one-hop readahead, so populate + // its current data from the readahead and the readahead from + // further down the long unwindowed frame. + + //!!! (This causes us to get out of sync when inhop changes - is + //!!! it better to vary outhop?) auto &classifyScale = cd->scales.at(classify); ClassificationReadaheadData &readahead = cd->readahead; m_scaleData.at(classify)->analysisWindow.cut - (buf + (longest - classify) / 2 + m_inhop, + (buf + (longest - classify) / 2 + inhop, readahead.timeDomain.data()); // Finally window the longest scale @@ -485,7 +496,7 @@ R3StretcherImpl::analyseChannel(int c, int prevOuthop) (classifyScale->mag.data(), 3, nullptr, classifyScale->troughs.data()); - double instantaneousRatio = double(prevOuthop) / double(m_inhop); + double instantaneousRatio = double(prevOuthop) / double(inhop); m_guide.calculate(instantaneousRatio, classifyScale->mag.data(), classifyScale->troughs.data(), diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index bc70bff..6b2e25b 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -181,8 +181,8 @@ protected: Parameters m_parameters; - double m_timeRatio; - double m_pitchScale; + std::atomic m_timeRatio; + std::atomic m_pitchScale; std::vector> m_channelData; std::map> m_scaleData; @@ -192,13 +192,13 @@ protected: Peak> m_troughPicker; std::unique_ptr m_calculator; std::unique_ptr m_resampler; - int m_inhop; + std::atomic m_inhop; int m_prevOuthop; bool m_draining; void consume(); void calculateHop(); - void analyseChannel(int channel, int prevOuthop); + void analyseChannel(int channel, int inhop, int prevOuthop); void synthesiseChannel(int channel, int outhop); double getEffectiveRatio() const { From a04b6adb1095f4808a3a6f49d4d17f14fdd89992 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 25 May 2022 14:43:05 +0100 Subject: [PATCH 031/184] Niemitalo window --- src/common/Window.h | 67 ++++++++++++++++++++++++++++++++++++- src/finer/R3StretcherImpl.h | 26 ++++++++++++-- 2 files changed, 89 insertions(+), 4 deletions(-) diff --git a/src/common/Window.h b/src/common/Window.h index d2cd2f6..af4d11e 100644 --- a/src/common/Window.h +++ b/src/common/Window.h @@ -28,6 +28,8 @@ #include #include +#include + #include "sysutils.h" #include "VectorOps.h" #include "Allocators.h" @@ -43,7 +45,9 @@ enum WindowType { GaussianWindow, ParzenWindow, NuttallWindow, - BlackmanHarrisWindow + BlackmanHarrisWindow, + NiemitaloForwardWindow, + NiemitaloReverseWindow }; template @@ -178,6 +182,67 @@ 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); + std::cerr << k << "," << m_cache[k-1] << std::endl; + } + 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]; + std::cerr << k << "," << m_cache[k-1] << std::endl; + } + for (int i = 0; i < quarter; ++i) { + m_cache[k++] = 0.0; + std::cerr << k << "," << m_cache[k-1] << std::endl; + } + + 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; diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 6b2e25b..8d549f8 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -168,15 +168,35 @@ protected: }; struct ScaleData { + int fftSize; FFT fft; Window analysisWindow; Window synthesisWindow; GuidedPhaseAdvance guided; ScaleData(GuidedPhaseAdvance::Parameters guidedParameters) : - fft(guidedParameters.fftSize), - analysisWindow(HannWindow, guidedParameters.fftSize), - synthesisWindow(HannWindow, guidedParameters.fftSize/2), + fftSize(guidedParameters.fftSize), + fft(fftSize), + analysisWindow(analysisWindowShape(fftSize), + analysisWindowLength(fftSize)), + synthesisWindow(synthesisWindowShape(fftSize), + synthesisWindowLength(fftSize)), guided(guidedParameters) { } + + WindowType analysisWindowShape(int fftSize) { + if (fftSize == 4096) return HannWindow; + else return NiemitaloForwardWindow; + } + int analysisWindowLength(int fftSize) { + return fftSize; + } + WindowType synthesisWindowShape(int fftSize) { + if (fftSize == 4096) return HannWindow; + else return NiemitaloReverseWindow; + } + int synthesisWindowLength(int fftSize) { + if (fftSize == 4096) return fftSize/2; + else return fftSize; + } }; Parameters m_parameters; From c3309f56c1f31ba31e03bdbc7e00ef93456132b7 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 25 May 2022 14:43:31 +0100 Subject: [PATCH 032/184] Remove debug output --- src/common/Window.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/common/Window.h b/src/common/Window.h index af4d11e..58a2eeb 100644 --- a/src/common/Window.h +++ b/src/common/Window.h @@ -28,8 +28,6 @@ #include #include -#include - #include "sysutils.h" #include "VectorOps.h" #include "Allocators.h" @@ -220,18 +218,15 @@ void Window::encache() - 0.05777077835678736534 * sin(9.0 * x) + 0.00920984524892982936 * cos(10.0 * x) + 0.01501989089735343216 * sin(10.0 * x); - std::cerr << k << "," << m_cache[k-1] << std::endl; } 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]; - std::cerr << k << "," << m_cache[k-1] << std::endl; } for (int i = 0; i < quarter; ++i) { m_cache[k++] = 0.0; - std::cerr << k << "," << m_cache[k-1] << std::endl; } if (m_type == NiemitaloReverseWindow) { From 4c878340dfa15d27f9bc6f07af9fb2d05c04f085 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 25 May 2022 16:24:51 +0100 Subject: [PATCH 033/184] This is a little faster, and I can't tell the difference at first listen, nor think of a good reason why it should be worse. To be tested. --- src/finer/PhaseAdvance.h | 36 +++++++----------------------------- 1 file changed, 7 insertions(+), 29 deletions(-) diff --git a/src/finer/PhaseAdvance.h b/src/finer/PhaseAdvance.h index 69e2df8..fc3a479 100644 --- a/src/finer/PhaseAdvance.h +++ b/src/finer/PhaseAdvance.h @@ -127,27 +127,10 @@ public: nullptr); } - m_peakPicker.findNearestAndNextPeaks(m_prevInMag[c], - lowest, highest - lowest + 1, - 2, m_prevPeaks[c], - nullptr); - -/* - static int counter = 0; - if (c == 0) { - if (++counter > 140 && counter < 150) { - std::cout << "Magnitudes and peaks (fftSize = " << m_parameters.fftSize << "):" << std::endl; - for (int i = 0; i < bs; ++i) { - if (m_currentPeaks[c][i] == i) { - std::cout << "*"; - } - std::cout << mag[c][i] << ", "; - } - std::cout << std::endl; - } - } -*/ - +// m_peakPicker.findNearestAndNextPeaks(m_prevInMag[c], +// lowest, highest - lowest + 1, +// 2, m_prevPeaks[c], +// nullptr); } if (channels > 1) { @@ -230,15 +213,10 @@ public: m_prevOutPhase[c][i] = outPhase[c][i]; } } - - //!!! NB in the original we use a different value of p for - //!!! peak-picking the prior magnitudes - this isn't carried - //!!! over here - it is now but I don't think this was the - //!!! full cause of our burbling -// int **tmp = m_prevPeaks; -// m_prevPeaks = m_currentPeaks; -// m_currentPeaks = tmp; + int **tmp = m_prevPeaks; + m_prevPeaks = m_currentPeaks; + m_currentPeaks = tmp; } protected: From fa004562f718179ba2ce49e64b89ec439c0865a9 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 25 May 2022 16:25:03 +0100 Subject: [PATCH 034/184] Tidy, and format comments --- src/finer/PhaseAdvance.h | 27 +++++++++++-------- src/finer/R3StretcherImpl.cpp | 51 +++++++++++++++++------------------ 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/src/finer/PhaseAdvance.h b/src/finer/PhaseAdvance.h index fc3a479..624cfa0 100644 --- a/src/finer/PhaseAdvance.h +++ b/src/finer/PhaseAdvance.h @@ -48,18 +48,23 @@ public: GuidedPhaseAdvance(Parameters parameters) : m_parameters(parameters), - m_blockSize(parameters.fftSize / 2 + 1), - m_peakPicker(m_blockSize), + m_binCount(parameters.fftSize / 2 + 1), + m_peakPicker(m_binCount), m_reported(false) { size_t ch = m_parameters.channels; - m_currentPeaks = allocate_and_zero_channels(ch, m_blockSize); - m_prevPeaks = allocate_and_zero_channels(ch, m_blockSize); - m_greatestChannel = allocate_and_zero(m_blockSize); - //!!! there is also a prevMag in R3StretcherImpl which could be passed in to here instead - m_prevInMag = allocate_and_zero_channels(ch, m_blockSize); - m_prevInPhase = allocate_and_zero_channels(ch, m_blockSize); - m_prevOutPhase = allocate_and_zero_channels(ch, m_blockSize); - m_unlocked = allocate_and_zero_channels(ch, m_blockSize); + 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_prevInMag = allocate_and_zero_channels(ch, 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() { @@ -221,7 +226,7 @@ public: protected: Parameters m_parameters; - int m_blockSize; + int m_binCount; Peak m_peakPicker; int **m_currentPeaks; int **m_prevPeaks; diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index d637064..9a1e66e 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -371,12 +371,12 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevOuthop) 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) + // 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; @@ -406,10 +406,9 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevOuthop) // 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 + // 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 v_fftshift(readahead.timeDomain.data(), classify); @@ -455,8 +454,8 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevOuthop) } } - // For the others we operate directly in the scale data - // and restrict the range for cartesian-polar conversion + // For the others we operate directly in the scale data and + // restrict the range for cartesian-polar conversion for (auto &it: cd->scales) { int fftSize = it.first; @@ -485,9 +484,8 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevOuthop) } } - // Use the classification scale to get a bin segmentation - // and calculate the adaptive frequency guide for this - // channel + // Use the classification scale to get a bin segmentation and + // calculate the adaptive frequency guide for this channel cd->prevSegmentation = cd->segmentation; cd->segmentation = cd->nextSegmentation; cd->nextSegmentation = cd->segmenter->segment(readahead.mag.data()); @@ -541,9 +539,9 @@ R3StretcherImpl::synthesiseChannel(int c, int outhop) } winscale = double(outhop) / winscale; - // The frequency filter is applied naively in the - // frequency domain. Aliasing is reduced by the - // shorter resynthesis window + // The frequency filter is applied naively in the frequency + // domain. Aliasing is reduced by the shorter resynthesis + // window double factor = m_parameters.sampleRate / double(fftSize); for (int i = 0; i < fftSize/2 + 1; ++i) { @@ -557,9 +555,9 @@ R3StretcherImpl::synthesiseChannel(int c, int outhop) } } - // Resynthesise each FFT size (scale) individually, then - // sum. This is easier to manage scaling for in situations - // with a varying resynthesis hop + // Resynthesise each FFT size (scale) individually, then sum. This + // is easier to manage scaling for in situations with a varying + // resynthesis hop for (auto &it : cd->scales) { int fftSize = it.first; @@ -587,12 +585,11 @@ R3StretcherImpl::synthesiseChannel(int c, int outhop) v_fftshift(scale->timeDomain.data(), fftSize); - // Synthesis window is 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 + // 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; From b07f74e5b9c989ae41d12b3ef2eb3869d4c066c3 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 26 May 2022 15:07:43 +0100 Subject: [PATCH 035/184] Add faster/finer option --- rubberband/RubberBandStretcher.h | 5 ++++- src/RubberBandStretcher.cpp | 25 ++++++++++++++++--------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/rubberband/RubberBandStretcher.h b/rubberband/RubberBandStretcher.h index 4b8355b..14529b9 100644 --- a/rubberband/RubberBandStretcher.h +++ b/rubberband/RubberBandStretcher.h @@ -317,7 +317,10 @@ public: OptionPitchHighConsistency = 0x04000000, OptionChannelsApart = 0x00000000, - OptionChannelsTogether = 0x10000000 + OptionChannelsTogether = 0x10000000, + + OptionEngineFaster = 0x00000000, + OptionEngineFiner = 0x20000000 // n.b. Options is int, so we must stop before 0x80000000 }; diff --git a/src/RubberBandStretcher.cpp b/src/RubberBandStretcher.cpp index 4a6d130..c94257c 100644 --- a/src/RubberBandStretcher.cpp +++ b/src/RubberBandStretcher.cpp @@ -27,21 +27,23 @@ namespace RubberBand { +//#define FASTER 1 RubberBandStretcher::RubberBandStretcher(size_t sampleRate, size_t channels, Options options, double initialTimeRatio, double initialPitchScale) : - /*!!! - m_d(new Impl(sampleRate, channels, options, - initialTimeRatio, initialPitchScale)) - */ - m_d(nullptr), - //!!! +logger - m_r3d(new R3StretcherImpl(R3StretcherImpl::Parameters(sampleRate, channels), - initialTimeRatio, - initialPitchScale)) + m_d + (!(options & OptionEngineFiner) ? + new Impl(sampleRate, channels, options, + initialTimeRatio, initialPitchScale) + : nullptr), + m_r3d + ((options & OptionEngineFiner) ? + new R3StretcherImpl(R3StretcherImpl::Parameters(sampleRate, channels), + initialTimeRatio, initialPitchScale) + : nullptr) { } @@ -183,6 +185,7 @@ float RubberBandStretcher::getFrequencyCutoff(int n) const { if (m_d) return m_d->getFrequencyCutoff(n); + else return {}; } void @@ -195,24 +198,28 @@ size_t RubberBandStretcher::getInputIncrement() const { if (m_d) return m_d->getInputIncrement(); + else return {}; } std::vector RubberBandStretcher::getOutputIncrements() const { if (m_d) return m_d->getOutputIncrements(); + else return {}; } std::vector RubberBandStretcher::getPhaseResetCurve() const { if (m_d) return m_d->getPhaseResetCurve(); + else return {}; } std::vector RubberBandStretcher::getExactTimePoints() const { if (m_d) return m_d->getExactTimePoints(); + else return {}; } size_t From 83f2b7607b77f03cfaacfef0ab266c09ac7a55c5 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 26 May 2022 15:08:07 +0100 Subject: [PATCH 036/184] An attempt to do the right thing when the hop changes - but this is not enough --- src/finer/Guide.h | 1 - src/finer/R3StretcherImpl.cpp | 98 +++++++++++++++++++++++++++-------- src/finer/R3StretcherImpl.h | 22 +++----- 3 files changed, 82 insertions(+), 39 deletions(-) diff --git a/src/finer/Guide.h b/src/finer/Guide.h index 9c36f34..ef79b62 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -119,7 +119,6 @@ public: m_maxLower(1100.0), m_maxHigher(7000.0) { double rate = m_parameters.sampleRate; - double nyquist = rate / 2.0; int bandFftSize = roundUp(int(ceil(rate/16.0))); m_configuration.fftBandLimits[0] = diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index d637064..ea5ddf0 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -91,6 +91,7 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, calculateHop(); + m_prevInhop = m_inhop; m_prevOuthop = int(round(m_inhop * getEffectiveRatio())); if (!m_inhop.is_lock_free()) { @@ -101,6 +102,36 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, } } +WindowType +R3StretcherImpl::ScaleData::analysisWindowShape(int fftSize) +{ +//!!! return HannWindow; + if (fftSize == 4096) return HannWindow; + else return NiemitaloForwardWindow; +} + +int +R3StretcherImpl::ScaleData::analysisWindowLength(int fftSize) +{ + return fftSize; +} + +WindowType +R3StretcherImpl::ScaleData::synthesisWindowShape(int fftSize) +{ +//!!! return HannWindow; + if (fftSize == 4096) return HannWindow; + else return NiemitaloReverseWindow; +} + +int +R3StretcherImpl::ScaleData::synthesisWindowLength(int fftSize) +{ +//!!! return fftSize/2; + if (fftSize == 4096) return fftSize/2; + else return fftSize; +} + void R3StretcherImpl::setTimeRatio(double ratio) { @@ -133,6 +164,8 @@ R3StretcherImpl::calculateHop() } m_inhop = int(round(inhop)); + + std::cout << "R3StretcherImpl::calculateHop: inhop " << m_inhop << ", mean outhop " << m_inhop * ratio << std::endl; } double @@ -168,6 +201,7 @@ R3StretcherImpl::reset() size_t R3StretcherImpl::getSamplesRequired() const { + if (available() != 0) return 0; int longest = m_guideConfiguration.longestFftSize; size_t rs = m_channelData[0]->inbuf->getReadSpace(); if (rs < longest) { @@ -260,6 +294,15 @@ R3StretcherImpl::consume() // std::cout << "outhop = " << outhop << std::endl; + if (inhop != m_prevInhop) { + std::cout << "Note: inhop has changed from " << m_prevInhop + << " to " << inhop << std::endl; + } + if (outhop != m_prevOuthop) { + std::cout << "Note: outhop has changed from " << m_prevOuthop + << " to " << outhop << std::endl; + } + while (m_channelData.at(0)->outbuf->getWriteSpace() >= outhop) { // NB our ChannelData, ScaleData, and ChannelScaleData maps @@ -282,7 +325,7 @@ R3StretcherImpl::consume() // Analysis for (int c = 0; c < channels; ++c) { - analyseChannel(c, inhop, m_prevOuthop); + analyseChannel(c, inhop, m_prevInhop, m_prevOuthop); } // Phase update. This is synchronised across all channels @@ -303,7 +346,7 @@ R3StretcherImpl::consume() m_channelAssembly.phase.data(), m_guideConfiguration, m_channelAssembly.guidance.data(), - inhop, + m_prevInhop, //!!! or inhop? outhop); } @@ -351,11 +394,12 @@ R3StretcherImpl::consume() } } + m_prevInhop = inhop; m_prevOuthop = outhop; } void -R3StretcherImpl::analyseChannel(int c, int inhop, int prevOuthop) +R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) { int longest = m_guideConfiguration.longestFftSize; int classify = m_guideConfiguration.classificationFftSize; @@ -387,11 +431,7 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevOuthop) } // The classification scale has a one-hop readahead, so populate - // its current data from the readahead and the readahead from - // further down the long unwindowed frame. - - //!!! (This causes us to get out of sync when inhop changes - is - //!!! it better to vary outhop?) + // the readahead from further down the long unwindowed frame. auto &classifyScale = cd->scales.at(classify); ClassificationReadaheadData &readahead = cd->readahead; @@ -399,6 +439,17 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevOuthop) 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... + + if (inhop != prevInhop) { + 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); @@ -406,20 +457,22 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevOuthop) // 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 + // 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) v_fftshift(readahead.timeDomain.data(), classify); - v_copy(classifyScale->mag.data(), - readahead.mag.data(), - classifyScale->bufSize); - - v_copy(classifyScale->phase.data(), - readahead.phase.data(), - classifyScale->bufSize); + if (inhop == prevInhop) { + v_copy(classifyScale->mag.data(), + readahead.mag.data(), + classifyScale->bufSize); + v_copy(classifyScale->phase.data(), + readahead.phase.data(), + classifyScale->bufSize); + } m_scaleData.at(classify)->fft.forward(readahead.timeDomain.data(), classifyScale->real.data(), @@ -455,12 +508,13 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevOuthop) } } - // For the others we operate directly in the scale data - // and restrict the range for cartesian-polar conversion + // For the others (and the classify if the inhop has changed) 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) continue; + if (fftSize == classify && inhop == prevInhop) continue; auto &scale = it.second; v_fftshift(scale->timeDomain.data(), fftSize); diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 8d549f8..b14160c 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -182,21 +182,10 @@ protected: synthesisWindowLength(fftSize)), guided(guidedParameters) { } - WindowType analysisWindowShape(int fftSize) { - if (fftSize == 4096) return HannWindow; - else return NiemitaloForwardWindow; - } - int analysisWindowLength(int fftSize) { - return fftSize; - } - WindowType synthesisWindowShape(int fftSize) { - if (fftSize == 4096) return HannWindow; - else return NiemitaloReverseWindow; - } - int synthesisWindowLength(int fftSize) { - if (fftSize == 4096) return fftSize/2; - else return fftSize; - } + WindowType analysisWindowShape(int fftSize); + int analysisWindowLength(int fftSize); + WindowType synthesisWindowShape(int fftSize); + int synthesisWindowLength(int fftSize); }; Parameters m_parameters; @@ -213,12 +202,13 @@ protected: std::unique_ptr m_calculator; std::unique_ptr m_resampler; std::atomic m_inhop; + int m_prevInhop; int m_prevOuthop; bool m_draining; void consume(); void calculateHop(); - void analyseChannel(int channel, int inhop, int prevOuthop); + void analyseChannel(int channel, int inhop, int prevInhop, int prevOuthop); void synthesiseChannel(int channel, int outhop); double getEffectiveRatio() const { From 2c23d52c94b757c02f8be8994271fd4e8a9d3228 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 26 May 2022 17:42:45 +0100 Subject: [PATCH 037/184] Clarify --- src/finer/R3StretcherImpl.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 9c4236e..514e62e 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -231,9 +231,8 @@ R3StretcherImpl::process(const float *const *input, size_t samples, bool final) m_parameters.logger("R3StretcherImpl::process: WARNING: Forced to increase input buffer size. Either setMaxProcessSize was not properly called or process is being called repeatedly without retrieve."); size_t newSize = m_channelData[0]->inbuf->getSize() - ws + samples; for (int c = 0; c < m_parameters.channels; ++c) { - m_channelData[c]->inbuf = - std::unique_ptr> - (m_channelData[c]->inbuf->resized(newSize)); + auto newBuf = m_channelData[c]->inbuf->resized(newSize); + m_channelData[c]->inbuf = std::unique_ptr>(newBuf); } } From a278acc5d2e3fa6d61b595fb4a87ba3a779cd222 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 26 May 2022 17:42:53 +0100 Subject: [PATCH 038/184] Print note, and use finer mode --- main/main.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/main/main.cpp b/main/main.cpp index 25e66ac..7e576e1 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -499,6 +499,11 @@ int main(int argc, char **argv) } RubberBandStretcher::Options options = 0; + + //!!! + cerr << "\n\n\n\n\n*** WARNING - THIS IS A TEST VERSION ONLY\n\n\n\n\n" << endl; + options = RubberBandStretcher::OptionEngineFiner; + if (realtime) options |= RubberBandStretcher::OptionProcessRealTime; if (!lamination) options |= RubberBandStretcher::OptionPhaseIndependent; if (longwin) options |= RubberBandStretcher::OptionWindowLong; From 84cd061e9d3c8ed5db955433c157739082d5cf3d Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 26 May 2022 17:46:13 +0100 Subject: [PATCH 039/184] Avoid duplicate bin in the different scales --- src/finer/R3StretcherImpl.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 9c4236e..4521f33 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -597,12 +597,13 @@ R3StretcherImpl::synthesiseChannel(int c, int outhop) // The frequency filter is applied naively in the frequency // domain. Aliasing is reduced by the shorter resynthesis // window - - double factor = m_parameters.sampleRate / double(fftSize); + + int lowBin = int(floor(fftSize * band.f0 / m_parameters.sampleRate)); + int highBin = int(floor(fftSize * band.f1 / m_parameters.sampleRate)); + if (highBin % 2 == 1) --highBin; + for (int i = 0; i < fftSize/2 + 1; ++i) { - double f = double(i) * factor; - if (f >= band.f0 && f < band.f1) { - //!!! check the mod 2 bit from stretch-fn + if (i >= lowBin && i < highBin) { scale->mag[i] *= winscale; } else { scale->mag[i] = 0.f; From 580d28afd2844731d6655079d1240aac31328bac Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 27 May 2022 10:06:31 +0100 Subject: [PATCH 040/184] Correct the use of in/out hop vs prev in/out hop --- src/finer/R3StretcherImpl.cpp | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 15dae5f..8b88fb0 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -291,6 +291,19 @@ R3StretcherImpl::consume() longest, longest); + // 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. + // std::cout << "outhop = " << outhop << std::endl; if (inhop != m_prevInhop) { @@ -345,8 +358,8 @@ R3StretcherImpl::consume() m_channelAssembly.phase.data(), m_guideConfiguration, m_channelAssembly.guidance.data(), - m_prevInhop, //!!! or inhop? - outhop); + m_prevInhop, + m_prevOuthop); } // Resynthesis @@ -391,10 +404,10 @@ R3StretcherImpl::consume() cd->inbuf->skip(inhop); } } - } - m_prevInhop = inhop; - m_prevOuthop = outhop; + m_prevInhop = inhop; + m_prevOuthop = outhop; + } } void @@ -548,7 +561,7 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) (classifyScale->mag.data(), 3, nullptr, classifyScale->troughs.data()); - double instantaneousRatio = double(prevOuthop) / double(inhop); + double instantaneousRatio = double(prevOuthop) / double(prevInhop); m_guide.calculate(instantaneousRatio, classifyScale->mag.data(), classifyScale->troughs.data(), From 9ecc601a2c14b51f406569267fcef34e989416b6 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 27 May 2022 11:17:20 +0100 Subject: [PATCH 041/184] Implement reset --- src/finer/PhaseAdvance.h | 8 ++++++++ src/finer/R3StretcherImpl.cpp | 27 +++++++++++++++++++++------ src/finer/R3StretcherImpl.h | 18 ++++++++++++++++++ 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/finer/PhaseAdvance.h b/src/finer/PhaseAdvance.h index 624cfa0..0d26320 100644 --- a/src/finer/PhaseAdvance.h +++ b/src/finer/PhaseAdvance.h @@ -77,6 +77,14 @@ public: deallocate_channels(m_prevOutPhase, ch); deallocate_channels(m_unlocked, ch); } + + void reset() { + size_t ch = m_parameters.channels; + v_zero_channels(m_prevPeaks, ch, m_binCount); + v_zero_channels(m_prevInMag, 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, diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 8b88fb0..9491f03 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -195,7 +195,19 @@ R3StretcherImpl::getChannelCount() const void R3StretcherImpl::reset() { - //!!! + m_calculator->reset(); + 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())); } size_t @@ -303,8 +315,6 @@ R3StretcherImpl::consume() // 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. - -// std::cout << "outhop = " << outhop << std::endl; if (inhop != m_prevInhop) { std::cout << "Note: inhop has changed from " << m_prevInhop @@ -457,7 +467,10 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) // rather than classification) anew rather than reuse the previous // readahead. Pity... - if (inhop != prevInhop) { + bool haveValidReadahead = cd->haveReadahead; + if (inhop != prevInhop) haveValidReadahead = false; + + if (!haveValidReadahead) { m_scaleData.at(classify)->analysisWindow.cut (buf + (longest - classify) / 2, classifyScale->timeDomain.data()); @@ -477,7 +490,7 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) v_fftshift(readahead.timeDomain.data(), classify); - if (inhop == prevInhop) { + if (haveValidReadahead) { v_copy(classifyScale->mag.data(), readahead.mag.data(), classifyScale->bufSize); @@ -520,13 +533,15 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) } } + cd->haveReadahead = true; + // For the others (and the classify if the inhop has changed) 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 && inhop == prevInhop) continue; + if (fftSize == classify && haveValidReadahead) continue; auto &scale = it.second; v_fftshift(scale->timeDomain.data(), fftSize); diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index b14160c..c25bbb6 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -119,6 +119,11 @@ protected: 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; @@ -127,6 +132,7 @@ protected: struct ChannelData { std::map> scales; ClassificationReadaheadData readahead; + bool haveReadahead; std::unique_ptr segmenter; BinSegmenter::Segmentation segmentation; BinSegmenter::Segmentation prevSegmentation; @@ -143,6 +149,7 @@ protected: int outRingBufferSize) : scales(), readahead(segmenterParameters.fftSize), + haveReadahead(false), segmenter(new BinSegmenter(segmenterParameters, classifierParameters)), segmentation(), prevSegmentation(), nextSegmentation(), @@ -150,6 +157,17 @@ protected: resampled(outRingBufferSize, 0.f), inbuf(new RingBuffer(inRingBufferSize)), outbuf(new RingBuffer(outRingBufferSize)) { } + void reset() { + haveReadahead = false; + segmentation = BinSegmenter::Segmentation(); + prevSegmentation = BinSegmenter::Segmentation(); + nextSegmentation = BinSegmenter::Segmentation(); + inbuf->reset(); + outbuf->reset(); + for (auto &s : scales) { + s.second->reset(); + } + } }; struct ChannelAssembly { From 937990898267837a82cabfd62cebd794c46583ac Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 27 May 2022 11:34:35 +0100 Subject: [PATCH 042/184] Avoid excessive outhop when pitch shifting a long way --- src/finer/R3StretcherImpl.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 9491f03..f19fed0 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -150,7 +150,10 @@ void R3StretcherImpl::calculateHop() { double ratio = getEffectiveRatio(); - double proposedOuthop = 256; + double proposedOuthop = 256.0; + if (proposedOuthop * m_pitchScale > 2048.0) { + proposedOuthop = 2048.0 / m_pitchScale; + } double inhop = 1.0; if (ratio > 1.0) { @@ -165,7 +168,7 @@ R3StretcherImpl::calculateHop() m_inhop = int(round(inhop)); - std::cout << "R3StretcherImpl::calculateHop: inhop " << m_inhop << ", mean outhop " << m_inhop * ratio << std::endl; + std::cout << "R3StretcherImpl::calculateHop: inhop = " << m_inhop << ", proposed outhop = " << proposedOuthop << ", mean outhop = " << m_inhop * ratio << std::endl; } double From 4d4bc7b4c3918734165b0df4da5d849ab53d6435 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 27 May 2022 11:45:28 +0100 Subject: [PATCH 043/184] Short-circuit slightly (in output, not so much in efficiency) when ratio is 1 --- src/finer/Guide.h | 42 ++++++++++++++++++++++++++++++++-------- src/finer/PhaseAdvance.h | 4 +++- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/finer/Guide.h b/src/finer/Guide.h index ef79b62..14fbb22 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -126,7 +126,7 @@ public: bandFftSize = roundUp(int(ceil(rate/32.0))); m_configuration.fftBandLimits[1] = - BandLimits(bandFftSize, rate, m_minLower, m_maxHigher); + BandLimits(bandFftSize, rate, 0.0, m_maxHigher); bandFftSize = roundUp(int(ceil(rate/64.0))); m_configuration.fftBandLimits[2] = @@ -146,13 +146,36 @@ public: const BinSegmenter::Segmentation &nextSegmentation, Guidance &guidance) const { - bool potentialKick = checkPotentialKick(magnitudes, prevMagnitudes); - guidance.kick.present = false; guidance.lowPercussive.present = false; guidance.highPercussive.present = false; guidance.phaseReset.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))); + + if (fabs(ratio - 1.0) < 1.0e-6) { + guidance.fftBands[0].f0 = 0.0; + guidance.fftBands[0].f1 = 0.0; + guidance.fftBands[1].f0 = 0.0; + guidance.fftBands[1].f1 = m_minHigher; + guidance.fftBands[2].f0 = m_minHigher; + guidance.fftBands[2].f1 = nyquist; + for (int i = 0; i < 5; ++i) { + guidance.phaseLockBands[i].p = 0; + guidance.phaseLockBands[i].beta = 1.0; + guidance.phaseLockBands[i].f0 = nyquist; + guidance.phaseLockBands[i].f1 = nyquist; + } + guidance.phaseLockBands[0].f0 = 0.0; + guidance.phaseLockBands[0].f1 = nyquist; + guidance.channelLock.present = false; + return; + } + guidance.channelLock.present = true; guidance.channelLock.f0 = 0.0; guidance.channelLock.f1 = 600.0; @@ -162,6 +185,8 @@ public: guidance.lowPercussive.f0 = 0.0; guidance.lowPercussive.f1 = segmentation.percussiveBelow; } + + bool potentialKick = checkPotentialKick(magnitudes, prevMagnitudes); if (potentialKick && prevSegmentation.percussiveBelow < 40.0) { guidance.kick = guidance.lowPercussive; @@ -195,17 +220,12 @@ public: double lower = snapToTrough(m_defaultLower, troughs); if (lower > m_maxLower) lower = m_maxLower; - double nyquist = m_parameters.sampleRate / 2.0; - - guidance.fftBands[0].fftSize = roundUp(int(ceil(nyquist/8.0))); guidance.fftBands[0].f0 = 0.0; guidance.fftBands[0].f1 = lower; - guidance.fftBands[1].fftSize = roundUp(int(ceil(nyquist/16.0))); guidance.fftBands[1].f0 = lower; guidance.fftBands[1].f1 = higher; - guidance.fftBands[2].fftSize = roundUp(int(ceil(nyquist/32.0))); guidance.fftBands[2].f0 = higher; guidance.fftBands[2].f1 = nyquist; @@ -231,6 +251,12 @@ public: guidance.phaseLockBands[3].f0 = higher; guidance.phaseLockBands[3].f1 = nyquist; + // Currently unused + guidance.phaseLockBands[4].p = 0; + guidance.phaseLockBands[4].beta = 1.0; + guidance.phaseLockBands[4].f0 = nyquist; + guidance.phaseLockBands[4].f1 = nyquist; + /* std::ostringstream str; str << "Guidance: FFT bands: [" diff --git a/src/finer/PhaseAdvance.h b/src/finer/PhaseAdvance.h index 0d26320..320a3a7 100644 --- a/src/finer/PhaseAdvance.h +++ b/src/finer/PhaseAdvance.h @@ -183,7 +183,9 @@ public: ++phaseLockBand; } double ph = 0.0; - if (inRange(f, g->phaseReset) || inRange(f, g->kick)) { + if (inhop == outhop) { + ph = m_unlocked[c][i]; + } else if (inRange(f, g->phaseReset) || inRange(f, g->kick)) { ph = phase[c][i]; } else if (inRange (f, g->highPercussive)) { ph = m_unlocked[c][i]; From f13d96a474c35fe969ac897950e6574e52fb73ca Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 27 May 2022 14:58:42 +0100 Subject: [PATCH 044/184] First cut at formant preservation --- src/RubberBandStretcher.cpp | 3 +- src/finer/BinSegmenter.h | 23 +++---- src/finer/Guide.h | 3 +- src/finer/R3StretcherImpl.cpp | 119 ++++++++++++++++++++++++++++++++-- src/finer/R3StretcherImpl.h | 31 ++++++++- 5 files changed, 156 insertions(+), 23 deletions(-) diff --git a/src/RubberBandStretcher.cpp b/src/RubberBandStretcher.cpp index c94257c..c021730 100644 --- a/src/RubberBandStretcher.cpp +++ b/src/RubberBandStretcher.cpp @@ -41,7 +41,8 @@ RubberBandStretcher::RubberBandStretcher(size_t sampleRate, : nullptr), m_r3d ((options & OptionEngineFiner) ? - new R3StretcherImpl(R3StretcherImpl::Parameters(sampleRate, channels), + new R3StretcherImpl(R3StretcherImpl::Parameters + (sampleRate, channels, options), initialTimeRatio, initialPitchScale) : nullptr) { diff --git a/src/finer/BinSegmenter.h b/src/finer/BinSegmenter.h index f6636aa..20efb46 100644 --- a/src/finer/BinSegmenter.h +++ b/src/finer/BinSegmenter.h @@ -50,23 +50,18 @@ public: fftSize(_fftSize), sampleRate(_sampleRate) { } }; - BinSegmenter(Parameters parameters, - BinClassifier::Parameters classifierParameters) : + BinSegmenter(Parameters parameters) : m_parameters(parameters), - m_classifierParameters(classifierParameters), - m_classifier(classifierParameters), - m_classification(classifierParameters.binCount, - BinClassifier::Classification::Silent), - m_numeric(classifierParameters.binCount, 0), - m_classFilter(classifierParameters.binCount / 64) + m_binCount(m_parameters.fftSize/2 + 1), + m_numeric(m_binCount, 0), + m_classFilter(m_binCount / 64) { } - Segmentation segment(const double *const mag) { - int n = m_classifierParameters.binCount; - m_classifier.classify(mag, m_classification.data()); + Segmentation segment(const BinClassifier::Classification *classification) { + int n = m_binCount; for (int i = 0; i < n; ++i) { - switch (m_classification[i]) { + switch (classification[i]) { case BinClassifier::Classification::Harmonic: m_numeric[i] = 0; break; case BinClassifier::Classification::Percussive: @@ -108,9 +103,7 @@ public: protected: Parameters m_parameters; - BinClassifier::Parameters m_classifierParameters; - BinClassifier m_classifier; - std::vector m_classification; + int m_binCount; std::vector m_numeric; MovingMedian m_classFilter; diff --git a/src/finer/Guide.h b/src/finer/Guide.h index 14fbb22..1541198 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -144,6 +144,7 @@ public: const BinSegmenter::Segmentation &segmentation, const BinSegmenter::Segmentation &prevSegmentation, const BinSegmenter::Segmentation &nextSegmentation, + bool specialCaseUnity, Guidance &guidance) const { guidance.kick.present = false; @@ -157,7 +158,7 @@ public: guidance.fftBands[1].fftSize = roundUp(int(ceil(nyquist/16.0))); guidance.fftBands[2].fftSize = roundUp(int(ceil(nyquist/32.0))); - if (fabs(ratio - 1.0) < 1.0e-6) { + if (specialCaseUnity && (fabs(ratio - 1.0) < 1.0e-6)) { guidance.fftBands[0].f0 = 0.0; guidance.fftBands[0].f1 = 0.0; guidance.fftBands[1].f0 = 0.0; diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index f19fed0..6c3839a 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -88,7 +88,10 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, resamplerParameters.maxBufferSize = m_guideConfiguration.longestFftSize; //!!!??? m_resampler = std::unique_ptr (new Resampler(resamplerParameters, m_parameters.channels)); - + + m_formant = std::unique_ptr + (new FormantData(m_guideConfiguration.classificationFftSize)); + calculateHop(); m_prevInhop = m_inhop; @@ -146,6 +149,27 @@ R3StretcherImpl::setPitchScale(double scale) calculateHop(); } +void +R3StretcherImpl::setFormantOption(RubberBandStretcher::Options options) +{ + int mask = (RubberBandStretcher::OptionFormantShifted | + RubberBandStretcher::OptionFormantPreserved); + m_parameters.options &= ~mask; + options &= mask; + m_parameters.options |= options; +} + +void +R3StretcherImpl::setPitchOption(RubberBandStretcher::Options options) +{ + int mask = (RubberBandStretcher::OptionPitchHighQuality | + RubberBandStretcher::OptionPitchHighSpeed | + RubberBandStretcher::OptionPitchHighConsistency); + m_parameters.options &= ~mask; + options &= mask; + m_parameters.options |= options; +} + void R3StretcherImpl::calculateHop() { @@ -353,6 +377,10 @@ R3StretcherImpl::consume() analyseChannel(c, inhop, m_prevInhop, m_prevOuthop); } + if (m_parameters.options & RubberBandStretcher::OptionFormantPreserved) { + analyseFormant(); + } + // Phase update. This is synchronised across all channels for (auto &it : m_channelData[0]->scales) { @@ -538,8 +566,9 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) cd->haveReadahead = true; - // For the others (and the classify if the inhop has changed) we - // operate directly in the scale data and restrict the range for + // 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) { @@ -571,15 +600,26 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) // 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(readahead.mag.data()); + cd->nextSegmentation = cd->segmenter->segment(cd->nextClassification.data()); m_troughPicker.findNearestAndNextPeaks (classifyScale->mag.data(), 3, nullptr, classifyScale->troughs.data()); double instantaneousRatio = double(prevOuthop) / double(prevInhop); +//!!!??? bool specialCaseUnity = !(m_parameters.options & +// RubberBandStretcher::OptionPitchHighConsistency); + + bool specialCaseUnity = true; + m_guide.calculate(instantaneousRatio, classifyScale->mag.data(), classifyScale->troughs.data(), @@ -587,9 +627,70 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) cd->segmentation, cd->prevSegmentation, cd->nextSegmentation, + specialCaseUnity, cd->guidance); } +void +R3StretcherImpl::analyseFormant() +{ + int classify = m_guideConfiguration.classificationFftSize; + int binCount = classify/2 + 1; + int channels = m_parameters.channels; + + auto &f = *m_formant; + + v_zero(f.envelope.data(), binCount); + + for (int c = 0; c < channels; ++c) { + auto &cd = m_channelData.at(c); + auto &scale = cd->scales.at(classify); + for (int i = 0; i < binCount; ++i) { + f.envelope.at(i) += scale->mag.at(i); + } + } + + m_scaleData.at(classify)->fft.inverseCepstral + (f.envelope.data(), f.cepstra.data()); + + int cutoff = int(floor(m_parameters.sampleRate / 700.0)); + if (cutoff < 1) cutoff = 1; + + f.cepstra[0] /= 2.0; + f.cepstra[cutoff-1] /= 2.0; + for (int i = cutoff; i < classify; ++i) { + f.cepstra[i] = 0.0; + } + v_scale(f.cepstra.data(), 1.0 / double(classify), cutoff); + + m_scaleData.at(classify)->fft.forward + (f.cepstra.data(), f.envelope.data(), + f.shifted.data()); // shifted is just a spare for this one + + v_exp(f.envelope.data(), binCount); + + for (int i = 0; i < binCount; ++i) { + if (f.envelope[i] > 1.0e10) f.envelope[i] = 1.0e10; + } + + double scale = m_pitchScale; + for (int target = 0; target < binCount; ++target) { + int source = int(round(target * scale)); + if (source >= binCount) { + f.shifted[target] = 0.0; + } else { + f.shifted[target] = f.envelope[source]; + } + } + + std::cout << "X:"; + for (int i = 0; i < binCount; ++i) { + if (i > 0) std::cout << ","; + std::cout << f.shifted[i]; + } + std::cout << std::endl; +} + void R3StretcherImpl::synthesiseChannel(int c, int outhop) { @@ -598,12 +699,22 @@ R3StretcherImpl::synthesiseChannel(int c, int outhop) auto &cd = m_channelData.at(c); for (auto &it : cd->scales) { + auto &scale = it.second; int bufSize = scale->bufSize; + // copy to prevMag before filtering v_copy(scale->prevMag.data(), scale->mag.data(), bufSize); + + // formant shift only the middle register + if (m_parameters.options & RubberBandStretcher::OptionFormantPreserved) { + if (it.first == m_guideConfiguration.classificationFftSize) { + v_divide(scale->mag.data(), m_formant->envelope.data(), bufSize); + v_multiply(scale->mag.data(), m_formant->shifted.data(), bufSize); + } + } } for (const auto &band : cd->guidance.fftBands) { diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index c25bbb6..fee814c 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -36,6 +36,8 @@ #include "../common/Allocators.h" #include "../common/Window.h" +#include "../rubberband/RubberBandStretcher.h" + #include #include #include @@ -49,8 +51,10 @@ public: struct Parameters { double sampleRate; int channels; + RubberBandStretcher::Options options; std::function logger; Parameters(double _sampleRate, int _channels, + RubberBandStretcher::Options options, std::function _log = &logCout) : sampleRate(_sampleRate), channels(_channels), logger(_log) { } }; @@ -68,6 +72,9 @@ public: double getTimeRatio() const; double getPitchScale() const; + void setFormantOption(RubberBandStretcher::Options); + void setPitchOption(RubberBandStretcher::Options); + size_t getSamplesRequired() const; void process(const float *const *input, size_t samples, bool final); int available() const; @@ -133,6 +140,9 @@ protected: 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; @@ -150,8 +160,12 @@ protected: scales(), readahead(segmenterParameters.fftSize), haveReadahead(false), - segmenter(new BinSegmenter(segmenterParameters, - classifierParameters)), + classifier(new BinClassifier(classifierParameters)), + classification(classifierParameters.binCount, + BinClassifier::Classification::Silent), + nextClassification(classifierParameters.binCount, + BinClassifier::Classification::Silent), + segmenter(new BinSegmenter(segmenterParameters)), segmentation(), prevSegmentation(), nextSegmentation(), mixdown(longestFftSize, 0.f), // though it could be shorter resampled(outRingBufferSize, 0.f), @@ -205,6 +219,17 @@ protected: WindowType synthesisWindowShape(int fftSize); int synthesisWindowLength(int fftSize); }; + + struct FormantData { + FixedVector cepstra; + FixedVector envelope; + FixedVector shifted; + + FormantData(int _fftSize) : + cepstra(_fftSize, 0.0), + envelope(_fftSize, 0.0), + shifted(_fftSize, 0.0) { } + }; Parameters m_parameters; @@ -219,6 +244,7 @@ protected: Peak> m_troughPicker; std::unique_ptr m_calculator; std::unique_ptr m_resampler; + std::unique_ptr m_formant; std::atomic m_inhop; int m_prevInhop; int m_prevOuthop; @@ -227,6 +253,7 @@ protected: void consume(); void calculateHop(); void analyseChannel(int channel, int inhop, int prevInhop, int prevOuthop); + void analyseFormant(); void synthesiseChannel(int channel, int outhop); double getEffectiveRatio() const { From 7febc09dbcf723f11663d232b18166a942de5816 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 27 May 2022 15:03:40 +0100 Subject: [PATCH 045/184] Fix accidental time stretch coming along with pitch shift --- src/finer/R3StretcherImpl.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 6c3839a..c7a29d1 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -315,16 +315,20 @@ R3StretcherImpl::retrieve(float *const *output, size_t samples) const void R3StretcherImpl::consume() { - double ratio = getEffectiveRatio(); - int inhop = m_inhop; - int longest = m_guideConfiguration.longestFftSize; int channels = m_parameters.channels; // m_calculator->setDebugLevel(3); + + int inhop = m_inhop; + + double effectivePitchRatio = 1.0 / m_pitchScale; + if (m_resampler) { + effectivePitchRatio = m_resampler->getEffectiveRatio(effectivePitchRatio); + } - int outhop = m_calculator->calculateSingle(ratio, - 1.0 / m_pitchScale, + int outhop = m_calculator->calculateSingle(m_timeRatio, + effectivePitchRatio, 1.f, inhop, longest, From ce64122ffeb9e26986a19dfd15867bcad6487569 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 27 May 2022 15:16:10 +0100 Subject: [PATCH 046/184] Wire up formant option; some tidying --- src/RubberBandStretcher.cpp | 4 ++++ src/finer/R3StretcherImpl.cpp | 25 ++++++++++++++----------- src/finer/R3StretcherImpl.h | 2 ++ 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/RubberBandStretcher.cpp b/src/RubberBandStretcher.cpp index c021730..5e75498 100644 --- a/src/RubberBandStretcher.cpp +++ b/src/RubberBandStretcher.cpp @@ -96,6 +96,8 @@ RubberBandStretcher::getLatency() const else return m_r3d->getLatency(); } +//!!! review all these + void RubberBandStretcher::setTransientsOption(Options options) { @@ -118,12 +120,14 @@ void RubberBandStretcher::setFormantOption(Options options) { if (m_d) m_d->setFormantOption(options); + else if (m_r3d) m_r3d->setFormantOption(options); } void RubberBandStretcher::setPitchOption(Options options) { if (m_d) m_d->setPitchOption(options); + else if (m_r3d) m_r3d->setPitchOption(options); } void diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index c7a29d1..0a19320 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -192,7 +192,7 @@ R3StretcherImpl::calculateHop() m_inhop = int(round(inhop)); - std::cout << "R3StretcherImpl::calculateHop: inhop = " << m_inhop << ", proposed outhop = " << proposedOuthop << ", mean outhop = " << m_inhop * ratio << std::endl; +// std::cout << "R3StretcherImpl::calculateHop: inhop = " << m_inhop << ", proposed outhop = " << proposedOuthop << ", mean outhop = " << m_inhop * ratio << std::endl; } double @@ -346,7 +346,7 @@ R3StretcherImpl::consume() // 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) { std::cout << "Note: inhop has changed from " << m_prevInhop << " to " << inhop << std::endl; @@ -355,7 +355,7 @@ R3StretcherImpl::consume() std::cout << "Note: outhop has changed from " << m_prevOuthop << " to " << outhop << std::endl; } - +*/ while (m_channelData.at(0)->outbuf->getWriteSpace() >= outhop) { // NB our ChannelData, ScaleData, and ChannelScaleData maps @@ -382,7 +382,10 @@ R3StretcherImpl::consume() } if (m_parameters.options & RubberBandStretcher::OptionFormantPreserved) { + m_formant->enabled = true; analyseFormant(); + } else { + m_formant->enabled = false; } // Phase update. This is synchronised across all channels @@ -687,12 +690,12 @@ R3StretcherImpl::analyseFormant() } } - std::cout << "X:"; - for (int i = 0; i < binCount; ++i) { - if (i > 0) std::cout << ","; - std::cout << f.shifted[i]; - } - std::cout << std::endl; +// std::cout << "X:"; +// for (int i = 0; i < binCount; ++i) { +// if (i > 0) std::cout << ","; +// std::cout << f.shifted[i]; +// } +// std::cout << std::endl; } void @@ -712,8 +715,8 @@ R3StretcherImpl::synthesiseChannel(int c, int outhop) scale->mag.data(), bufSize); - // formant shift only the middle register - if (m_parameters.options & RubberBandStretcher::OptionFormantPreserved) { + if (m_formant->enabled) { + // formant shift only the middle register if (it.first == m_guideConfiguration.classificationFftSize) { v_divide(scale->mag.data(), m_formant->envelope.data(), bufSize); v_multiply(scale->mag.data(), m_formant->shifted.data(), bufSize); diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index fee814c..b702025 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -221,11 +221,13 @@ protected: }; struct FormantData { + bool enabled; FixedVector cepstra; FixedVector envelope; FixedVector shifted; FormantData(int _fftSize) : + enabled(false), cepstra(_fftSize, 0.0), envelope(_fftSize, 0.0), shifted(_fftSize, 0.0) { } From 2673983220cb0ee009953410b6f16b226eb4ba7b Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 27 May 2022 15:29:28 +0100 Subject: [PATCH 047/184] I think this check was only necessary because of our wrong handling of pitch scale --- src/finer/R3StretcherImpl.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 0a19320..ae1c3e6 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -175,9 +175,9 @@ R3StretcherImpl::calculateHop() { double ratio = getEffectiveRatio(); double proposedOuthop = 256.0; - if (proposedOuthop * m_pitchScale > 2048.0) { - proposedOuthop = 2048.0 / m_pitchScale; - } +//!!! if (proposedOuthop * m_pitchScale > 2048.0) { +// proposedOuthop = 2048.0 / m_pitchScale; +// } double inhop = 1.0; if (ratio > 1.0) { @@ -192,6 +192,8 @@ R3StretcherImpl::calculateHop() m_inhop = int(round(inhop)); + //!!! but if we now have outhop > 4096 ever, we will crash, so we must check + // std::cout << "R3StretcherImpl::calculateHop: inhop = " << m_inhop << ", proposed outhop = " << proposedOuthop << ", mean outhop = " << m_inhop * ratio << std::endl; } From c33582a8c4d894c9be566eaeae89ced719a27ddc Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Sun, 5 Jun 2022 18:50:52 +0100 Subject: [PATCH 048/184] Fix failure to initialise options --- src/finer/R3StretcherImpl.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index b702025..5d6fdec 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -54,9 +54,10 @@ public: RubberBandStretcher::Options options; std::function logger; Parameters(double _sampleRate, int _channels, - RubberBandStretcher::Options options, + RubberBandStretcher::Options _options, std::function _log = &logCout) : - sampleRate(_sampleRate), channels(_channels), logger(_log) { } + sampleRate(_sampleRate), channels(_channels), options(_options), + logger(_log) { } }; R3StretcherImpl(Parameters parameters, From baab6ae66ef8f161b32c22cf803d59fdeb6ea071 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 6 Jun 2022 12:08:52 +0100 Subject: [PATCH 049/184] Further formant experiments --- src/finer/R3StretcherImpl.cpp | 45 +++++++++++++++++++++++++---------- src/finer/R3StretcherImpl.h | 11 +++++++++ 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index ae1c3e6..52db249 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -681,7 +681,8 @@ R3StretcherImpl::analyseFormant() for (int i = 0; i < binCount; ++i) { if (f.envelope[i] > 1.0e10) f.envelope[i] = 1.0e10; } - + + //!!! double scale = m_pitchScale; for (int target = 0; target < binCount; ++target) { int source = int(round(target * scale)); @@ -717,13 +718,13 @@ R3StretcherImpl::synthesiseChannel(int c, int outhop) scale->mag.data(), bufSize); - if (m_formant->enabled) { +// if (m_formant->enabled) { // formant shift only the middle register - if (it.first == m_guideConfiguration.classificationFftSize) { - v_divide(scale->mag.data(), m_formant->envelope.data(), bufSize); - v_multiply(scale->mag.data(), m_formant->shifted.data(), bufSize); - } - } +// if (it.first == m_guideConfiguration.classificationFftSize) { +// v_divide(scale->mag.data(), m_formant->envelope.data(), bufSize); +// v_multiply(scale->mag.data(), m_formant->shifted.data(), bufSize); +// } +// } } for (const auto &band : cd->guidance.fftBands) { @@ -748,17 +749,35 @@ R3StretcherImpl::synthesiseChannel(int c, int outhop) // domain. Aliasing is reduced by the shorter resynthesis // window + //!!! I don't think we have binForFrequency etc available in + //!!! this class - really that's ridiculous + int lowBin = int(floor(fftSize * band.f0 / m_parameters.sampleRate)); int highBin = int(floor(fftSize * band.f1 / m_parameters.sampleRate)); if (highBin % 2 == 1) --highBin; - - for (int i = 0; i < fftSize/2 + 1; ++i) { - if (i >= lowBin && i < highBin) { - scale->mag[i] *= winscale; - } else { - scale->mag[i] = 0.f; + + int formantHigh = int(floor(fftSize * 7000.0 / m_parameters.sampleRate)); + for (int i = 0; i < lowBin; ++i) { + scale->mag[i] = 0.0; + } + if (m_formant->enabled) { + double targetFactor = double(m_formant->fftSize) / double(fftSize); + double sourceFactor = targetFactor * m_pitchScale; + double scaleFactor = 1.0 / targetFactor; + for (int i = lowBin; i < highBin && i < formantHigh; ++i) { + double source = m_formant->envelopeAt(i * sourceFactor); + double target = m_formant->envelopeAt(i * targetFactor); + if (target > 0.0) { + scale->mag[i] *= (source * source) / (target * target); + } } } + for (int i = lowBin; i < highBin; ++i) { + scale->mag[i] *= winscale; + } + for (int i = highBin; i < fftSize/2 + 1; ++i) { + scale->mag[i] = 0.0; + } } // Resynthesise each FFT size (scale) individually, then sum. This diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 5d6fdec..1132a0a 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -223,15 +223,26 @@ protected: struct FormantData { bool enabled; + int fftSize; FixedVector cepstra; FixedVector envelope; FixedVector shifted; FormantData(int _fftSize) : enabled(false), + fftSize(_fftSize), cepstra(_fftSize, 0.0), envelope(_fftSize, 0.0), shifted(_fftSize, 0.0) { } + + double envelopeAt(double bin) const { + int b0 = int(floor(bin)), b1 = int(ceil(bin)); + if (b1 == b0) return envelope.at(b0); + else { + double diff = bin - double(b0); + return envelope.at(b0) * (1.0 - diff) + envelope.at(b1) * diff; + } + } }; Parameters m_parameters; From f8abd0743883c58ba1a2a1e82a6f621dd0ef3e63 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 6 Jun 2022 13:09:29 +0100 Subject: [PATCH 050/184] Tidy, and try without a high formant limit --- src/finer/R3StretcherImpl.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 52db249..ff0ea77 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -655,7 +655,7 @@ R3StretcherImpl::analyseFormant() auto &cd = m_channelData.at(c); auto &scale = cd->scales.at(classify); for (int i = 0; i < binCount; ++i) { - f.envelope.at(i) += scale->mag.at(i); + f.envelope.at(i) += scale->mag.at(i) / double(channels); } } @@ -677,6 +677,7 @@ R3StretcherImpl::analyseFormant() f.shifted.data()); // shifted is just a spare for this one 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; @@ -764,11 +765,11 @@ R3StretcherImpl::synthesiseChannel(int c, int outhop) double targetFactor = double(m_formant->fftSize) / double(fftSize); double sourceFactor = targetFactor * m_pitchScale; double scaleFactor = 1.0 / targetFactor; - for (int i = lowBin; i < highBin && i < formantHigh; ++i) { + for (int i = lowBin; i < highBin /* && i < formantHigh */; ++i) { double source = m_formant->envelopeAt(i * sourceFactor); double target = m_formant->envelopeAt(i * targetFactor); if (target > 0.0) { - scale->mag[i] *= (source * source) / (target * target); + scale->mag[i] *= source / target; } } } From 0ceca73636419c2fb243f953d30c63637df8d7d1 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 6 Jun 2022 14:00:09 +0100 Subject: [PATCH 051/184] Settle on these values for the moment --- src/finer/R3StretcherImpl.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index ff0ea77..3250e4c 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -662,7 +662,7 @@ R3StretcherImpl::analyseFormant() m_scaleData.at(classify)->fft.inverseCepstral (f.envelope.data(), f.cepstra.data()); - int cutoff = int(floor(m_parameters.sampleRate / 700.0)); + int cutoff = int(floor(m_parameters.sampleRate / 650.0)); if (cutoff < 1) cutoff = 1; f.cepstra[0] /= 2.0; @@ -757,19 +757,21 @@ R3StretcherImpl::synthesiseChannel(int c, int outhop) int highBin = int(floor(fftSize * band.f1 / m_parameters.sampleRate)); if (highBin % 2 == 1) --highBin; - int formantHigh = int(floor(fftSize * 7000.0 / m_parameters.sampleRate)); + int formantHigh = int(floor(fftSize * 10000.0 / m_parameters.sampleRate)); for (int i = 0; i < lowBin; ++i) { scale->mag[i] = 0.0; } if (m_formant->enabled) { double targetFactor = double(m_formant->fftSize) / double(fftSize); double sourceFactor = targetFactor * m_pitchScale; - double scaleFactor = 1.0 / targetFactor; - for (int i = lowBin; i < highBin /* && i < formantHigh */; ++i) { + for (int i = lowBin; i < highBin && i < formantHigh; ++i) { double source = m_formant->envelopeAt(i * sourceFactor); double target = m_formant->envelopeAt(i * targetFactor); if (target > 0.0) { - scale->mag[i] *= source / target; + double ratio = source / target; +// if (ratio < 0.2) ratio = 0.2; +// if (ratio > 5.0) ratio = 5.0; + scale->mag[i] *= ratio; } } } From c728a650e95f07711b4d896fa16016769966cff4 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 6 Jun 2022 14:17:17 +0100 Subject: [PATCH 052/184] Remove unused array --- src/finer/PhaseAdvance.h | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/finer/PhaseAdvance.h b/src/finer/PhaseAdvance.h index 320a3a7..92e75ec 100644 --- a/src/finer/PhaseAdvance.h +++ b/src/finer/PhaseAdvance.h @@ -55,7 +55,6 @@ public: 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_prevInMag = allocate_and_zero_channels(ch, 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); @@ -72,7 +71,6 @@ public: deallocate_channels(m_currentPeaks, ch); deallocate_channels(m_prevPeaks, ch); deallocate(m_greatestChannel); - deallocate_channels(m_prevInMag, ch); deallocate_channels(m_prevInPhase, ch); deallocate_channels(m_prevOutPhase, ch); deallocate_channels(m_unlocked, ch); @@ -81,7 +79,6 @@ public: void reset() { size_t ch = m_parameters.channels; v_zero_channels(m_prevPeaks, ch, m_binCount); - v_zero_channels(m_prevInMag, ch, m_binCount); v_zero_channels(m_prevInPhase, ch, m_binCount); v_zero_channels(m_prevOutPhase, ch, m_binCount); } @@ -139,11 +136,6 @@ public: band.p, m_currentPeaks[c], nullptr); } - -// m_peakPicker.findNearestAndNextPeaks(m_prevInMag[c], -// lowest, highest - lowest + 1, -// 2, m_prevPeaks[c], -// nullptr); } if (channels > 1) { @@ -221,9 +213,6 @@ public: for (int i = lowest; i <= highest; ++i) { m_prevInPhase[c][i] = phase[c][i]; } - for (int i = lowest; i <= highest; ++i) { - m_prevInMag[c][i] = mag[c][i]; - } for (int i = lowest; i <= highest; ++i) { m_prevOutPhase[c][i] = outPhase[c][i]; } @@ -241,7 +230,6 @@ protected: int **m_currentPeaks; int **m_prevPeaks; int *m_greatestChannel; - double **m_prevInMag; double **m_prevInPhase; double **m_prevOutPhase; double **m_unlocked; From 9a7a977fa07db255db92ed58192d0985464cf1eb Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 6 Jun 2022 14:49:01 +0100 Subject: [PATCH 053/184] Tidy --- src/finer/R3StretcherImpl.cpp | 34 +++++----------------------------- src/finer/R3StretcherImpl.h | 13 ++++++++----- 2 files changed, 13 insertions(+), 34 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 3250e4c..51b760f 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -674,7 +674,7 @@ R3StretcherImpl::analyseFormant() m_scaleData.at(classify)->fft.forward (f.cepstra.data(), f.envelope.data(), - f.shifted.data()); // shifted is just a spare for this one + f.spare.data()); // shifted is just a spare for this one v_exp(f.envelope.data(), binCount); v_square(f.envelope.data(), binCount); @@ -682,24 +682,6 @@ R3StretcherImpl::analyseFormant() for (int i = 0; i < binCount; ++i) { if (f.envelope[i] > 1.0e10) f.envelope[i] = 1.0e10; } - - //!!! - double scale = m_pitchScale; - for (int target = 0; target < binCount; ++target) { - int source = int(round(target * scale)); - if (source >= binCount) { - f.shifted[target] = 0.0; - } else { - f.shifted[target] = f.envelope[source]; - } - } - -// std::cout << "X:"; -// for (int i = 0; i < binCount; ++i) { -// if (i > 0) std::cout << ","; -// std::cout << f.shifted[i]; -// } -// std::cout << std::endl; } void @@ -718,14 +700,6 @@ R3StretcherImpl::synthesiseChannel(int c, int outhop) v_copy(scale->prevMag.data(), scale->mag.data(), bufSize); - -// if (m_formant->enabled) { - // formant shift only the middle register -// if (it.first == m_guideConfiguration.classificationFftSize) { -// v_divide(scale->mag.data(), m_formant->envelope.data(), bufSize); -// v_multiply(scale->mag.data(), m_formant->shifted.data(), bufSize); -// } -// } } for (const auto &band : cd->guidance.fftBands) { @@ -764,13 +738,15 @@ R3StretcherImpl::synthesiseChannel(int c, int outhop) if (m_formant->enabled) { double targetFactor = double(m_formant->fftSize) / double(fftSize); double sourceFactor = targetFactor * m_pitchScale; + double maxRatio = 60.0; + double minRatio = 1.0 / maxRatio; for (int i = lowBin; i < highBin && i < formantHigh; ++i) { double source = m_formant->envelopeAt(i * sourceFactor); double target = m_formant->envelopeAt(i * targetFactor); if (target > 0.0) { double ratio = source / target; -// if (ratio < 0.2) ratio = 0.2; -// if (ratio > 5.0) ratio = 5.0; + if (ratio < minRatio) ratio = minRatio; + if (ratio > maxRatio) ratio = maxRatio; scale->mag[i] *= ratio; } } diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 1132a0a..a7eb39b 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -226,19 +226,22 @@ protected: int fftSize; FixedVector cepstra; FixedVector envelope; - FixedVector shifted; + FixedVector spare; FormantData(int _fftSize) : enabled(false), fftSize(_fftSize), cepstra(_fftSize, 0.0), - envelope(_fftSize, 0.0), - shifted(_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 (b1 == b0) return envelope.at(b0); - else { + 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; } From ec7a2b1b511ae4f2bf6edd024460927dee9847e5 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 6 Jun 2022 16:37:44 +0100 Subject: [PATCH 054/184] Experiment with formant shifting before the peaks/phase work --- src/finer/R3StretcherImpl.cpp | 61 +++++++++++++++++++++++++++++++++-- src/finer/R3StretcherImpl.h | 1 + 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 51b760f..5e2ac63 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -383,13 +383,15 @@ R3StretcherImpl::consume() analyseChannel(c, inhop, m_prevInhop, m_prevOuthop); } + //!!! + /* if (m_parameters.options & RubberBandStretcher::OptionFormantPreserved) { m_formant->enabled = true; analyseFormant(); } else { m_formant->enabled = false; } - + */ // Phase update. This is synchronised across all channels for (auto &it : m_channelData[0]->scales) { @@ -607,6 +609,15 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) } } + //!!! + if (m_parameters.options & RubberBandStretcher::OptionFormantPreserved) { + m_formant->enabled = true; + if (c == 0) analyseFormant(); + adjustFormant(c); + } else { + m_formant->enabled = false; + } + // Use the classification scale to get a bin segmentation and // calculate the adaptive frequency guide for this channel @@ -684,6 +695,45 @@ R3StretcherImpl::analyseFormant() } } +void +R3StretcherImpl::adjustFormant(int c) +{ + auto &cd = m_channelData.at(c); + + for (auto &it : cd->scales) { + + int fftSize = it.first; + auto &scale = it.second; + + /* + int lowBin = int(floor(fftSize * band.f0 / m_parameters.sampleRate)); + int highBin = int(floor(fftSize * band.f1 / m_parameters.sampleRate)); + if (highBin % 2 == 1) --highBin; + + int formantHigh = int(floor(fftSize * 10000.0 / m_parameters.sampleRate)); + for (int i = 0; i < lowBin; ++i) { + scale->mag[i] = 0.0; + } + */ + + double targetFactor = double(m_formant->fftSize) / double(fftSize); + double sourceFactor = targetFactor * m_pitchScale; +// double maxRatio = 60.0; +// double minRatio = 1.0 / maxRatio; +// for (int i = lowBin; i < highBin && i < formantHigh; ++i) { + for (int i = 0; i < scale->bufSize; ++i) { + double source = m_formant->envelopeAt(i * sourceFactor); + double target = m_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 R3StretcherImpl::synthesiseChannel(int c, int outhop) { @@ -701,6 +751,10 @@ R3StretcherImpl::synthesiseChannel(int c, int outhop) scale->mag.data(), bufSize); } + +// if (m_formant->enabled) { +// adjustFormant(c); +// } for (const auto &band : cd->guidance.fftBands) { int fftSize = band.fftSize; @@ -730,11 +784,13 @@ R3StretcherImpl::synthesiseChannel(int c, int outhop) int lowBin = int(floor(fftSize * band.f0 / m_parameters.sampleRate)); int highBin = int(floor(fftSize * band.f1 / m_parameters.sampleRate)); if (highBin % 2 == 1) --highBin; - +/* int formantHigh = int(floor(fftSize * 10000.0 / m_parameters.sampleRate)); +*/ for (int i = 0; i < lowBin; ++i) { scale->mag[i] = 0.0; } + /* if (m_formant->enabled) { double targetFactor = double(m_formant->fftSize) / double(fftSize); double sourceFactor = targetFactor * m_pitchScale; @@ -751,6 +807,7 @@ R3StretcherImpl::synthesiseChannel(int c, int outhop) } } } + */ for (int i = lowBin; i < highBin; ++i) { scale->mag[i] *= winscale; } diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index a7eb39b..91f3ae6 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -271,6 +271,7 @@ protected: void calculateHop(); void analyseChannel(int channel, int inhop, int prevInhop, int prevOuthop); void analyseFormant(); + void adjustFormant(int channel); void synthesiseChannel(int channel, int outhop); double getEffectiveRatio() const { From 4fb7b0ad476fe2908b17f44d0dde7f95ee528c53 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 6 Jun 2022 16:51:02 +0100 Subject: [PATCH 055/184] And per-channel formants --- src/finer/R3StretcherImpl.cpp | 65 +++++++++++------------------------ src/finer/R3StretcherImpl.h | 61 ++++++++++++++++---------------- 2 files changed, 52 insertions(+), 74 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 5e2ac63..9cf13d7 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -89,9 +89,6 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, m_resampler = std::unique_ptr (new Resampler(resamplerParameters, m_parameters.channels)); - m_formant = std::unique_ptr - (new FormantData(m_guideConfiguration.classificationFftSize)); - calculateHop(); m_prevInhop = m_inhop; @@ -383,15 +380,6 @@ R3StretcherImpl::consume() analyseChannel(c, inhop, m_prevInhop, m_prevOuthop); } - //!!! - /* - if (m_parameters.options & RubberBandStretcher::OptionFormantPreserved) { - m_formant->enabled = true; - analyseFormant(); - } else { - m_formant->enabled = false; - } - */ // Phase update. This is synchronised across all channels for (auto &it : m_channelData[0]->scales) { @@ -609,14 +597,13 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) } } - //!!! - if (m_parameters.options & RubberBandStretcher::OptionFormantPreserved) { - m_formant->enabled = true; - if (c == 0) analyseFormant(); - adjustFormant(c); - } else { - m_formant->enabled = false; - } + if (m_parameters.options & RubberBandStretcher::OptionFormantPreserved) { + cd->formant->enabled = true; + analyseFormant(c); + adjustFormant(c); + } else { + cd->formant->enabled = false; + } // Use the classification scale to get a bin segmentation and // calculate the adaptive frequency guide for this channel @@ -652,40 +639,30 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) } void -R3StretcherImpl::analyseFormant() +R3StretcherImpl::analyseFormant(int c) { - int classify = m_guideConfiguration.classificationFftSize; - int binCount = classify/2 + 1; - int channels = m_parameters.channels; + auto &cd = m_channelData.at(c); + auto &f = *cd->formant; - auto &f = *m_formant; - - v_zero(f.envelope.data(), binCount); + int fftSize = f.fftSize; + int binCount = fftSize/2 + 1; - for (int c = 0; c < channels; ++c) { - auto &cd = m_channelData.at(c); - auto &scale = cd->scales.at(classify); - for (int i = 0; i < binCount; ++i) { - f.envelope.at(i) += scale->mag.at(i) / double(channels); - } - } + auto &scale = cd->scales.at(fftSize); + auto &scaleData = m_scaleData.at(fftSize); - m_scaleData.at(classify)->fft.inverseCepstral - (f.envelope.data(), f.cepstra.data()); + 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 < classify; ++i) { + for (int i = cutoff; i < fftSize; ++i) { f.cepstra[i] = 0.0; } - v_scale(f.cepstra.data(), 1.0 / double(classify), cutoff); + v_scale(f.cepstra.data(), 1.0 / double(fftSize), cutoff); - m_scaleData.at(classify)->fft.forward - (f.cepstra.data(), f.envelope.data(), - f.spare.data()); // shifted is just a spare for this one + scaleData->fft.forward(f.cepstra.data(), f.envelope.data(), f.spare.data()); v_exp(f.envelope.data(), binCount); v_square(f.envelope.data(), binCount); @@ -716,14 +693,14 @@ R3StretcherImpl::adjustFormant(int c) } */ - double targetFactor = double(m_formant->fftSize) / double(fftSize); + double targetFactor = double(cd->formant->fftSize) / double(fftSize); double sourceFactor = targetFactor * m_pitchScale; // double maxRatio = 60.0; // double minRatio = 1.0 / maxRatio; // for (int i = lowBin; i < highBin && i < formantHigh; ++i) { for (int i = 0; i < scale->bufSize; ++i) { - double source = m_formant->envelopeAt(i * sourceFactor); - double target = m_formant->envelopeAt(i * targetFactor); + 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; diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 91f3ae6..d1c8d11 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -137,6 +137,33 @@ protected: ChannelScaleData &operator=(const ChannelScaleData &) =delete; }; + struct FormantData { + bool enabled; + int fftSize; + FixedVector cepstra; + FixedVector envelope; + FixedVector spare; + + FormantData(int _fftSize) : + enabled(false), + 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; @@ -153,6 +180,7 @@ protected: FixedVector resampled; std::unique_ptr> inbuf; std::unique_ptr> outbuf; + std::unique_ptr formant; ChannelData(BinSegmenter::Parameters segmenterParameters, BinClassifier::Parameters classifierParameters, int longestFftSize, @@ -171,7 +199,8 @@ protected: mixdown(longestFftSize, 0.f), // though it could be shorter resampled(outRingBufferSize, 0.f), inbuf(new RingBuffer(inRingBufferSize)), - outbuf(new RingBuffer(outRingBufferSize)) { } + outbuf(new RingBuffer(outRingBufferSize)), + formant(new FormantData(segmenterParameters.fftSize)) { } void reset() { haveReadahead = false; segmentation = BinSegmenter::Segmentation(); @@ -220,33 +249,6 @@ protected: WindowType synthesisWindowShape(int fftSize); int synthesisWindowLength(int fftSize); }; - - struct FormantData { - bool enabled; - int fftSize; - FixedVector cepstra; - FixedVector envelope; - FixedVector spare; - - FormantData(int _fftSize) : - enabled(false), - 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; - } - } - }; Parameters m_parameters; @@ -261,7 +263,6 @@ protected: Peak> m_troughPicker; std::unique_ptr m_calculator; std::unique_ptr m_resampler; - std::unique_ptr m_formant; std::atomic m_inhop; int m_prevInhop; int m_prevOuthop; @@ -270,7 +271,7 @@ protected: void consume(); void calculateHop(); void analyseChannel(int channel, int inhop, int prevInhop, int prevOuthop); - void analyseFormant(); + void analyseFormant(int channel); void adjustFormant(int channel); void synthesiseChannel(int channel, int outhop); From 100a7c0ba68e074ba701f61b45230d958577590e Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 6 Jun 2022 17:06:52 +0100 Subject: [PATCH 056/184] Go back to running formants only within the necessary range --- src/finer/R3StretcherImpl.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 9cf13d7..67801e9 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -682,30 +682,36 @@ R3StretcherImpl::adjustFormant(int c) int fftSize = it.first; auto &scale = it.second; + int formantHigh = int(floor(fftSize * 10000.0 / m_parameters.sampleRate)); + /* int lowBin = int(floor(fftSize * band.f0 / m_parameters.sampleRate)); int highBin = int(floor(fftSize * band.f1 / m_parameters.sampleRate)); if (highBin % 2 == 1) --highBin; - int formantHigh = int(floor(fftSize * 10000.0 / m_parameters.sampleRate)); for (int i = 0; i < lowBin; ++i) { scale->mag[i] = 0.0; } */ - + double targetFactor = double(cd->formant->fftSize) / double(fftSize); double sourceFactor = targetFactor * m_pitchScale; + + for (const auto &b : m_guideConfiguration.fftBandLimits) { + if (b.fftSize == fftSize) { // double maxRatio = 60.0; // double minRatio = 1.0 / maxRatio; // for (int i = lowBin; i < highBin && i < formantHigh; ++i) { - for (int i = 0; i < scale->bufSize; ++i) { - double source = cd->formant->envelopeAt(i * sourceFactor); - double target = cd->formant->envelopeAt(i * targetFactor); - if (target > 0.0) { - double ratio = source / target; + for (int i = b.b0min; i < b.b1max && i < formantHigh; ++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; + scale->mag[i] *= ratio; + } + } } } } From 6ef77ef7aab2bd0168c77addda4defeba688d6f3 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 6 Jun 2022 17:10:12 +0100 Subject: [PATCH 057/184] Tidy --- src/finer/R3StretcherImpl.cpp | 69 +++++++---------------------------- src/finer/R3StretcherImpl.h | 2 - 2 files changed, 13 insertions(+), 58 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 67801e9..a5b5570 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -598,11 +598,8 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) } if (m_parameters.options & RubberBandStretcher::OptionFormantPreserved) { - cd->formant->enabled = true; analyseFormant(c); adjustFormant(c); - } else { - cd->formant->enabled = false; } // Use the classification scale to get a bin segmentation and @@ -622,9 +619,6 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) classifyScale->troughs.data()); double instantaneousRatio = double(prevOuthop) / double(prevInhop); -//!!!??? bool specialCaseUnity = !(m_parameters.options & -// RubberBandStretcher::OptionPitchHighConsistency); - bool specialCaseUnity = true; m_guide.calculate(instantaneousRatio, @@ -682,35 +676,22 @@ R3StretcherImpl::adjustFormant(int c) int fftSize = it.first; auto &scale = it.second; - int formantHigh = int(floor(fftSize * 10000.0 / m_parameters.sampleRate)); - - /* - int lowBin = int(floor(fftSize * band.f0 / m_parameters.sampleRate)); - int highBin = int(floor(fftSize * band.f1 / m_parameters.sampleRate)); - if (highBin % 2 == 1) --highBin; - - for (int i = 0; i < lowBin; ++i) { - scale->mag[i] = 0.0; - } - */ - + int highBin = int(floor(fftSize * 10000.0 / m_parameters.sampleRate)); double targetFactor = double(cd->formant->fftSize) / double(fftSize); double sourceFactor = targetFactor * m_pitchScale; + double maxRatio = 60.0; + double minRatio = 1.0 / maxRatio; for (const auto &b : m_guideConfiguration.fftBandLimits) { - if (b.fftSize == fftSize) { -// double maxRatio = 60.0; -// double minRatio = 1.0 / maxRatio; -// for (int i = lowBin; i < highBin && i < formantHigh; ++i) { - for (int i = b.b0min; i < b.b1max && i < formantHigh; ++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; - } + 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; } } } @@ -734,10 +715,6 @@ R3StretcherImpl::synthesiseChannel(int c, int outhop) scale->mag.data(), bufSize); } - -// if (m_formant->enabled) { -// adjustFormant(c); -// } for (const auto &band : cd->guidance.fftBands) { int fftSize = band.fftSize; @@ -767,30 +744,10 @@ R3StretcherImpl::synthesiseChannel(int c, int outhop) int lowBin = int(floor(fftSize * band.f0 / m_parameters.sampleRate)); int highBin = int(floor(fftSize * band.f1 / m_parameters.sampleRate)); if (highBin % 2 == 1) --highBin; -/* - int formantHigh = int(floor(fftSize * 10000.0 / m_parameters.sampleRate)); -*/ + for (int i = 0; i < lowBin; ++i) { scale->mag[i] = 0.0; } - /* - if (m_formant->enabled) { - double targetFactor = double(m_formant->fftSize) / double(fftSize); - double sourceFactor = targetFactor * m_pitchScale; - double maxRatio = 60.0; - double minRatio = 1.0 / maxRatio; - for (int i = lowBin; i < highBin && i < formantHigh; ++i) { - double source = m_formant->envelopeAt(i * sourceFactor); - double target = m_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; - } - } - } - */ for (int i = lowBin; i < highBin; ++i) { scale->mag[i] *= winscale; } diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index d1c8d11..7079934 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -138,14 +138,12 @@ protected: }; struct FormantData { - bool enabled; int fftSize; FixedVector cepstra; FixedVector envelope; FixedVector spare; FormantData(int _fftSize) : - enabled(false), fftSize(_fftSize), cepstra(_fftSize, 0.0), envelope(_fftSize/2 + 1, 0.0), From 48174ded2013b20ee7160ddf8295dd9e6af63b5f Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 6 Jun 2022 17:44:22 +0100 Subject: [PATCH 058/184] Ensure we have full magnitude range available in classify scale even when ratio has changed and the readahead isn't valid - necessary to avoid noise on ratio change when formant shifting --- src/finer/R3StretcherImpl.cpp | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index a5b5570..bc68736 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -572,7 +572,10 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) for (auto &it: cd->scales) { int fftSize = it.first; - if (fftSize == classify && haveValidReadahead) continue; + if (fftSize == classify && haveValidReadahead) { + continue; + } + auto &scale = it.second; v_fftshift(scale->timeDomain.data(), fftSize); @@ -581,7 +584,28 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) scale->real.data(), scale->imag.data()); - //!!! This should be a map + // For the classify scale we always want the full range, as + // all the magnitudes (though not 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) { + //!!! and because not all the phases are relevant, there + //!!! is room for an optimisation here, though this is + //!!! used only when ratio changes + v_cartesian_to_polar(scale->mag.data(), + scale->phase.data(), + scale->real.data(), + scale->imag.data(), + fftSize/2 + 1); + v_scale(scale->mag.data(), + 1.0 / double(fftSize), + scale->mag.size()); + continue; + } + + //!!! should this be a map? for (const auto &b : m_guideConfiguration.fftBandLimits) { if (b.fftSize == fftSize) { v_cartesian_to_polar(scale->mag.data() + b.b0min, From ef2d39b3af1f55094ddf0e3411d2ee3530d7cfb6 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 6 Jun 2022 17:50:22 +0100 Subject: [PATCH 059/184] Or is this the right thing? I'm having trouble deciding --- src/finer/R3StretcherImpl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index bc68736..9783b43 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -767,7 +767,7 @@ R3StretcherImpl::synthesiseChannel(int c, int outhop) int lowBin = int(floor(fftSize * band.f0 / m_parameters.sampleRate)); int highBin = int(floor(fftSize * band.f1 / m_parameters.sampleRate)); - if (highBin % 2 == 1) --highBin; + if (highBin % 2 == 0 && highBin > 0) --highBin; for (int i = 0; i < lowBin; ++i) { scale->mag[i] = 0.0; From 7519ef47cc754a0373bc43b95c609ed06e1abcee Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 6 Jun 2022 21:53:54 +0100 Subject: [PATCH 060/184] Toward minimising MovingMedian a bit --- src/common/MovingMedian.h | 58 ++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/src/common/MovingMedian.h b/src/common/MovingMedian.h index c12b18e..9160e3d 100644 --- a/src/common/MovingMedian.h +++ b/src/common/MovingMedian.h @@ -41,15 +41,14 @@ class MovingMedian : public SampleFilter 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) { + m_frame(allocate_and_zero(size * 2)), + m_sorted(m_frame + size) + { setPercentile(percentile); } ~MovingMedian() { deallocate(m_frame); - deallocate(m_sorted); } void setPercentile(float p) { @@ -63,10 +62,10 @@ public: std::cerr << "WARNING: MovingMedian: NaN encountered" << std::endl; value = T(); } - drop(m_frame[0]); + T toDrop = m_frame[0]; v_move(m_frame, m_frame+1, P::m_size-1); m_frame[P::m_size-1] = value; - put(value); + dropAndPut(toDrop, value); } T get() const { @@ -102,24 +101,39 @@ public: 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); + void dropAndPut(const T &toDrop, const T &toPut) { + // precondition: m_sorted contains m_size values, one of which is toDrop + // postcondition: m_sorted contains m_size values, one of which is toPut + // (and one instance of toDrop has been removed) + int n = P::m_size; + int dropIx = std::lower_bound(m_sorted, m_sorted + n, toDrop) - m_sorted; +// if (m_sorted[dropIx] != toDrop) { +// throw std::runtime_error("not found"); +// } + int putIx; + if (toPut > toDrop) { + putIx = std::lower_bound(m_sorted + dropIx, m_sorted + n, toPut) - m_sorted; + } else if (toPut < toDrop) { + putIx = std::lower_bound(m_sorted, m_sorted + dropIx, toPut) - m_sorted; + } else { + m_sorted[dropIx] = toPut; + return; + } + if (putIx > dropIx) { + for (int i = dropIx; i+1 < putIx; ++i) { + m_sorted[i] = m_sorted[i+1]; + } + m_sorted[putIx-1] = toPut; + } else if (putIx < dropIx) { + for (int i = dropIx; i > putIx; --i) { + m_sorted[i] = m_sorted[i-1]; + } + m_sorted[putIx] = toPut; + } else { + m_sorted[putIx] = toPut; + } } MovingMedian(const MovingMedian &) =delete; From d7163b36f61b74c00a91ece60ebbcacadeeef50d Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 7 Jun 2022 08:50:25 +0100 Subject: [PATCH 061/184] Quicker moving median (max one call to lower_bound, we're copying the elements between the two values anyway so we might as well compare against them too) - this checks out --- src/common/MovingMedian.h | 67 +++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/src/common/MovingMedian.h b/src/common/MovingMedian.h index 9160e3d..929fe3c 100644 --- a/src/common/MovingMedian.h +++ b/src/common/MovingMedian.h @@ -30,6 +30,8 @@ #include #include +//#define DEBUG_MM 1 + namespace RubberBand { @@ -109,31 +111,56 @@ private: // (and one instance of toDrop has been removed) int n = P::m_size; int dropIx = std::lower_bound(m_sorted, m_sorted + n, toDrop) - m_sorted; -// if (m_sorted[dropIx] != toDrop) { -// throw std::runtime_error("not found"); -// } - int putIx; + +#ifdef DEBUG_MM + std::cout << "\nbefore: ["; + for (int i = 0; i < P::m_size; ++i) { + if (i > 0) std::cout << ","; + std::cout << m_sorted[i]; + } + std::cout << "]" << std::endl; + + std::cout << "toDrop = " << toDrop << ", dropIx = " << dropIx << std::endl; + std::cout << "toPut = " << toPut /* << ", putIx = " << putIx */ << std::endl; + if (m_sorted[dropIx] != toDrop) { + throw std::runtime_error("element not found"); + } +#endif + if (toPut > toDrop) { - putIx = std::lower_bound(m_sorted + dropIx, m_sorted + n, toPut) - m_sorted; - } else if (toPut < toDrop) { - putIx = std::lower_bound(m_sorted, m_sorted + dropIx, toPut) - m_sorted; - } else { - m_sorted[dropIx] = toPut; - return; - } - if (putIx > dropIx) { - for (int i = dropIx; i+1 < putIx; ++i) { + int i = dropIx; + while (i+1 < n) { + if (m_sorted[i+1] > toPut) { + break; + } m_sorted[i] = m_sorted[i+1]; + ++i; } - m_sorted[putIx-1] = toPut; - } else if (putIx < dropIx) { - for (int i = dropIx; i > putIx; --i) { - m_sorted[i] = m_sorted[i-1]; + m_sorted[i] = toPut; + } else if (toPut < toDrop) { + int i = dropIx; + while (i >= 0) { + --i; + if (i < 0 || m_sorted[i] < toPut) { + break; + } + m_sorted[i+1] = m_sorted[i]; } - m_sorted[putIx] = toPut; - } else { - m_sorted[putIx] = toPut; + m_sorted[i+1] = toPut; } + +#ifdef DEBUG_MM + std::cout << "after: ["; + for (int i = 0; i < P::m_size; ++i) { + if (i > 0) std::cout << ","; + std::cout << m_sorted[i]; + } + std::cout << "]" << std::endl; + + if (!std::is_sorted(m_sorted, m_sorted + n)) { + throw std::runtime_error("array is not sorted"); + } +#endif } MovingMedian(const MovingMedian &) =delete; From b8c7289c45d0e2fe2ba9b3ba7fab35e97fec973f Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 7 Jun 2022 08:53:42 +0100 Subject: [PATCH 062/184] Further small improvement to moving median --- src/common/MovingMedian.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/common/MovingMedian.h b/src/common/MovingMedian.h index 929fe3c..b574deb 100644 --- a/src/common/MovingMedian.h +++ b/src/common/MovingMedian.h @@ -110,7 +110,14 @@ private: // postcondition: m_sorted contains m_size values, one of which is toPut // (and one instance of toDrop has been removed) int n = P::m_size; - int dropIx = std::lower_bound(m_sorted, m_sorted + n, toDrop) - m_sorted; + int dropIx; + if (toDrop <= m_sorted[0]) { + // 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(m_sorted, m_sorted + n, toDrop) - m_sorted; + } #ifdef DEBUG_MM std::cout << "\nbefore: ["; From 459be9fbad2cbf04277754d8ba3d40d0a0066715 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 7 Jun 2022 09:02:00 +0100 Subject: [PATCH 063/184] Tiny simplification --- src/common/MovingMedian.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/common/MovingMedian.h b/src/common/MovingMedian.h index b574deb..c9a60ba 100644 --- a/src/common/MovingMedian.h +++ b/src/common/MovingMedian.h @@ -146,9 +146,8 @@ private: m_sorted[i] = toPut; } else if (toPut < toDrop) { int i = dropIx; - while (i >= 0) { - --i; - if (i < 0 || m_sorted[i] < toPut) { + while (true) { + if (--i < 0 || m_sorted[i] < toPut) { break; } m_sorted[i+1] = m_sorted[i]; From a564a7c812cc438534d6a82727d155ea1e1a5adf Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 7 Jun 2022 09:06:37 +0100 Subject: [PATCH 064/184] Minor tidy --- src/common/MovingMedian.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/MovingMedian.h b/src/common/MovingMedian.h index c9a60ba..37a2d98 100644 --- a/src/common/MovingMedian.h +++ b/src/common/MovingMedian.h @@ -128,7 +128,7 @@ private: std::cout << "]" << std::endl; std::cout << "toDrop = " << toDrop << ", dropIx = " << dropIx << std::endl; - std::cout << "toPut = " << toPut /* << ", putIx = " << putIx */ << std::endl; + std::cout << "toPut = " << toPut << std::endl; if (m_sorted[dropIx] != toDrop) { throw std::runtime_error("element not found"); } From c31839ef93cf0832e9ab4b949df543e249b659c2 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 7 Jun 2022 09:06:52 +0100 Subject: [PATCH 065/184] Use fixed (faster) resampler settings in offline mode --- src/finer/R3StretcherImpl.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 9783b43..0d42995 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -82,8 +82,16 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, Resampler::Parameters resamplerParameters; resamplerParameters.quality = Resampler::FastestTolerable; - resamplerParameters.dynamism = Resampler::RatioOftenChanging; - resamplerParameters.ratioChange = Resampler::SmoothRatioChange; + + if (m_parameters.options & RubberBandStretcher::OptionProcessRealTime) { + 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; + } + resamplerParameters.initialSampleRate = m_parameters.sampleRate; resamplerParameters.maxBufferSize = m_guideConfiguration.longestFftSize; //!!!??? m_resampler = std::unique_ptr From 0cd622d0da9cdc37ae48ad4f05e12da30c5b9f47 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 7 Jun 2022 09:50:33 +0100 Subject: [PATCH 066/184] Create a MovingMedianStack to contain a stack of filters with contiguous addressing - does not appear to be notably beneficial, though I quite like the api --- src/common/MovingMedian.h | 129 ++++++++++++++++++++++++++++++++++++ src/finer/BinClassifier.h | 18 ++--- src/finer/R3StretcherImpl.h | 1 + 3 files changed, 140 insertions(+), 8 deletions(-) diff --git a/src/common/MovingMedian.h b/src/common/MovingMedian.h index 37a2d98..b1eee68 100644 --- a/src/common/MovingMedian.h +++ b/src/common/MovingMedian.h @@ -25,6 +25,7 @@ #define RUBBERBAND_MOVING_MEDIAN_H #include "SampleFilter.h" +#include "FixedVector.h" #include "Allocators.h" #include @@ -35,6 +36,134 @@ namespace RubberBand { +template +class MovingMedianStack +{ +public: + MovingMedianStack(int nfilters, int filterSize, float percentile = 50.f) : + m_buffer(nfilters * filterSize * 2, {}), + m_size(filterSize) + { + setPercentile(percentile); + } + + ~MovingMedianStack() { + } + + void setPercentile(float p) { + m_index = int((m_size * p) / 100.f); + if (m_index >= m_size) m_index = m_size-1; + if (m_index < 0) m_index = 0; + } + + void push(int filter, T value) { + if (value != value) { + std::cerr << "WARNING: MovingMedian: NaN encountered" << std::endl; + value = T(); + } + T *frame = frameFor(filter); + T toDrop = frame[0]; + v_move(frame, frame+1, m_size-1); + frame[m_size-1] = value; + dropAndPut(filter, toDrop, value); + } + + T get(int filter) const { + const T *sorted = sortedFor(filter); + return sorted[m_index]; + } + + void reset() { + v_zero(m_buffer.data(), m_buffer.size()); + } + +private: + FixedVector m_buffer; + int m_size; + int m_index; + + const T *frameFor(int filter) const { + return m_buffer.data() + filter * m_size * 2; + } + T *frameFor(int filter) { + return m_buffer.data() + filter * m_size * 2; + } + const T *sortedFor(int filter) const { + return frameFor(filter) + m_size; + } + T *sortedFor(int filter) { + return frameFor(filter) + m_size; + } + + void dropAndPut(int filter, const T &toDrop, const T &toPut) { + // precondition: sorted contains m_size values, one of which is toDrop + // postcondition: sorted contains m_size values, one of which is toPut + // (and one instance of toDrop has been removed) + int n = m_size; + int dropIx; + T *sorted = sortedFor(filter); + if (toDrop <= sorted[0]) { + // 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; + } + +#ifdef DEBUG_MM + std::cout << "\nbefore: ["; + for (int i = 0; i < m_size; ++i) { + if (i > 0) std::cout << ","; + std::cout << sorted[i]; + } + std::cout << "]" << std::endl; + + std::cout << "toDrop = " << toDrop << ", dropIx = " << dropIx << std::endl; + std::cout << "toPut = " << toPut << std::endl; + if (sorted[dropIx] != toDrop) { + throw std::runtime_error("element not found"); + } +#endif + + 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; + } + +#ifdef DEBUG_MM + std::cout << "after: ["; + for (int i = 0; i < m_size; ++i) { + if (i > 0) std::cout << ","; + std::cout << sorted[i]; + } + std::cout << "]" << std::endl; + + if (!std::is_sorted(sorted, sorted + n)) { + throw std::runtime_error("array is not sorted"); + } +#endif + } + + MovingMedianStack(const MovingMedianStack &) =delete; + MovingMedianStack &operator=(const MovingMedianStack &) =delete; +}; + template class MovingMedian : public SampleFilter { diff --git a/src/finer/BinClassifier.h b/src/finer/BinClassifier.h index 73febee..105fcf8 100644 --- a/src/finer/BinClassifier.h +++ b/src/finer/BinClassifier.h @@ -66,16 +66,13 @@ public: 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; - for (int i = 0; i < n; ++i) { - m_hFilters.push_back(std::make_shared> - (m_parameters.horizontalFilterLength)); - } - m_hf = allocate_and_zero(n); m_vf = allocate_and_zero(n); @@ -96,12 +93,17 @@ public: deallocate(m_vf); } + void reset() + { + m_hFilters->reset(); + } + void classify(const double *const mag, Classification *classification) { const int n = m_parameters.binCount; for (int i = 0; i < n; ++i) { - m_hFilters[i]->push(mag[i]); - m_hf[i] = m_hFilters[i]->get(); + m_hFilters->push(i, mag[i]); + m_hf[i] = m_hFilters->get(i); } v_copy(m_vf, mag, n); @@ -134,7 +136,7 @@ public: protected: Parameters m_parameters; - std::vector>> m_hFilters; + std::unique_ptr> m_hFilters; std::unique_ptr> m_vFilter; // We manage the queued frames through pointer swapping, hence // bare pointers here diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 7079934..80e9bc9 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -201,6 +201,7 @@ protected: formant(new FormantData(segmenterParameters.fftSize)) { } void reset() { haveReadahead = false; + classifier->reset(); segmentation = BinSegmenter::Segmentation(); prevSegmentation = BinSegmenter::Segmentation(); nextSegmentation = BinSegmenter::Segmentation(); From 1bfd02c6f35654d9601eecdc0b0764bffc0e9b69 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 7 Jun 2022 10:06:30 +0100 Subject: [PATCH 067/184] Simplify by using MovingMedianStack to implement MovingMedian --- src/common/MovingMedian.h | 146 ++++++++++---------------------------- src/common/SampleFilter.h | 16 +---- 2 files changed, 39 insertions(+), 123 deletions(-) diff --git a/src/common/MovingMedian.h b/src/common/MovingMedian.h index b1eee68..8d742ab 100644 --- a/src/common/MovingMedian.h +++ b/src/common/MovingMedian.h @@ -40,9 +40,9 @@ template class MovingMedianStack { public: - MovingMedianStack(int nfilters, int filterSize, float percentile = 50.f) : - m_buffer(nfilters * filterSize * 2, {}), - m_size(filterSize) + MovingMedianStack(int nfilters, int filterLength, float percentile = 50.f) : + m_buffer(nfilters * filterLength * 2, {}), + m_length(filterLength) { setPercentile(percentile); } @@ -50,9 +50,17 @@ public: ~MovingMedianStack() { } + int getNFilters() const { + return m_buffer.size() / (m_length * 2); + } + + int getSize() const { + return m_length; + } + void setPercentile(float p) { - m_index = int((m_size * p) / 100.f); - if (m_index >= m_size) m_index = m_size-1; + m_index = int((m_length * p) / 100.f); + if (m_index >= m_length) m_index = m_length-1; if (m_index < 0) m_index = 0; } @@ -63,8 +71,8 @@ public: } T *frame = frameFor(filter); T toDrop = frame[0]; - v_move(frame, frame+1, m_size-1); - frame[m_size-1] = value; + v_move(frame, frame+1, m_length-1); + frame[m_length-1] = value; dropAndPut(filter, toDrop, value); } @@ -79,30 +87,30 @@ public: private: FixedVector m_buffer; - int m_size; + int m_length; int m_index; const T *frameFor(int filter) const { - return m_buffer.data() + filter * m_size * 2; + return m_buffer.data() + filter * m_length * 2; } T *frameFor(int filter) { - return m_buffer.data() + filter * m_size * 2; + return m_buffer.data() + filter * m_length * 2; } const T *sortedFor(int filter) const { - return frameFor(filter) + m_size; + return frameFor(filter) + m_length; } T *sortedFor(int filter) { - return frameFor(filter) + m_size; + return frameFor(filter) + m_length; } void dropAndPut(int filter, const T &toDrop, const T &toPut) { - // precondition: sorted contains m_size values, one of which is toDrop - // postcondition: sorted contains m_size values, one of which is toPut + // precondition: sorted contains m_length values, one of which is toDrop + // postcondition: sorted contains m_length values, one of which is toPut // (and one instance of toDrop has been removed) - int n = m_size; - int dropIx; + const int n = m_length; T *sorted = sortedFor(filter); - if (toDrop <= sorted[0]) { + 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; @@ -112,7 +120,7 @@ private: #ifdef DEBUG_MM std::cout << "\nbefore: ["; - for (int i = 0; i < m_size; ++i) { + for (int i = 0; i < n; ++i) { if (i > 0) std::cout << ","; std::cout << sorted[i]; } @@ -148,7 +156,7 @@ private: #ifdef DEBUG_MM std::cout << "after: ["; - for (int i = 0; i < m_size; ++i) { + for (int i = 0; i < n; ++i) { if (i > 0) std::cout << ","; std::cout << sorted[i]; } @@ -167,45 +175,33 @@ private: template class MovingMedian : public SampleFilter { - typedef SampleFilter P; - public: MovingMedian(int size, float percentile = 50.f) : - SampleFilter(size), - m_frame(allocate_and_zero(size * 2)), - m_sorted(m_frame + size) + m_mm(1, size, percentile) { - setPercentile(percentile); } - ~MovingMedian() { - deallocate(m_frame); + ~MovingMedian() { + } + + int getSize() const { + return m_mm.getSize(); } 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; + m_mm.setPercentile(p); } void push(T value) { - if (value != value) { - std::cerr << "WARNING: MovingMedian: NaN encountered" << std::endl; - value = T(); - } - T toDrop = m_frame[0]; - v_move(m_frame, m_frame+1, P::m_size-1); - m_frame[P::m_size-1] = value; - dropAndPut(toDrop, value); + m_mm.push(0, value); } T get() const { - return m_sorted[m_index]; + return m_mm.get(0); } void reset() { - v_zero(m_frame, P::m_size); - v_zero(m_sorted, P::m_size); + m_mm.reset(); } // Convenience function that applies a given filter to an array @@ -230,73 +226,7 @@ public: } private: - T *const m_frame; - T *const m_sorted; - int m_index; - - void dropAndPut(const T &toDrop, const T &toPut) { - // precondition: m_sorted contains m_size values, one of which is toDrop - // postcondition: m_sorted contains m_size values, one of which is toPut - // (and one instance of toDrop has been removed) - int n = P::m_size; - int dropIx; - if (toDrop <= m_sorted[0]) { - // 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(m_sorted, m_sorted + n, toDrop) - m_sorted; - } - -#ifdef DEBUG_MM - std::cout << "\nbefore: ["; - for (int i = 0; i < P::m_size; ++i) { - if (i > 0) std::cout << ","; - std::cout << m_sorted[i]; - } - std::cout << "]" << std::endl; - - std::cout << "toDrop = " << toDrop << ", dropIx = " << dropIx << std::endl; - std::cout << "toPut = " << toPut << std::endl; - if (m_sorted[dropIx] != toDrop) { - throw std::runtime_error("element not found"); - } -#endif - - if (toPut > toDrop) { - int i = dropIx; - while (i+1 < n) { - if (m_sorted[i+1] > toPut) { - break; - } - m_sorted[i] = m_sorted[i+1]; - ++i; - } - m_sorted[i] = toPut; - } else if (toPut < toDrop) { - int i = dropIx; - while (true) { - if (--i < 0 || m_sorted[i] < toPut) { - break; - } - m_sorted[i+1] = m_sorted[i]; - } - m_sorted[i+1] = toPut; - } - -#ifdef DEBUG_MM - std::cout << "after: ["; - for (int i = 0; i < P::m_size; ++i) { - if (i > 0) std::cout << ","; - std::cout << m_sorted[i]; - } - std::cout << "]" << std::endl; - - if (!std::is_sorted(m_sorted, m_sorted + n)) { - throw std::runtime_error("array is not sorted"); - } -#endif - } + MovingMedianStack m_mm; MovingMedian(const MovingMedian &) =delete; MovingMedian &operator=(const MovingMedian &) =delete; diff --git a/src/common/SampleFilter.h b/src/common/SampleFilter.h index 21bd18f..7dea368 100644 --- a/src/common/SampleFilter.h +++ b/src/common/SampleFilter.h @@ -33,24 +33,10 @@ 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 &); }; } From 8ed709d274357ede9c1b9e6a1e2d513dd5a4dcb6 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 7 Jun 2022 11:05:50 +0100 Subject: [PATCH 068/184] Don't run median filters for higher frequencies than we're going to use anyway --- src/finer/BinClassifier.h | 4 +++- src/finer/BinSegmenter.h | 17 +++++++---------- src/finer/R3StretcherImpl.cpp | 14 +++++++++++--- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/finer/BinClassifier.h b/src/finer/BinClassifier.h index 105fcf8..94b1fa6 100644 --- a/src/finer/BinClassifier.h +++ b/src/finer/BinClassifier.h @@ -98,7 +98,9 @@ public: m_hFilters->reset(); } - void classify(const double *const mag, Classification *classification) { + 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) { diff --git a/src/finer/BinSegmenter.h b/src/finer/BinSegmenter.h index 20efb46..00fb1e4 100644 --- a/src/finer/BinSegmenter.h +++ b/src/finer/BinSegmenter.h @@ -45,21 +45,21 @@ public: struct Parameters { int fftSize; + int binCount; double sampleRate; - Parameters(int _fftSize, double _sampleRate) : - fftSize(_fftSize), sampleRate(_sampleRate) { } + Parameters(int _fftSize, int _binCount, double _sampleRate) : + fftSize(_fftSize), binCount(_binCount), sampleRate(_sampleRate) { } }; BinSegmenter(Parameters parameters) : m_parameters(parameters), - m_binCount(m_parameters.fftSize/2 + 1), - m_numeric(m_binCount, 0), - m_classFilter(m_binCount / 64) + m_numeric(m_parameters.binCount, 0), + m_classFilter(16) { } Segmentation segment(const BinClassifier::Classification *classification) { - int n = m_binCount; + int n = m_parameters.binCount; for (int i = 0; i < n; ++i) { switch (classification[i]) { case BinClassifier::Classification::Harmonic: @@ -79,12 +79,10 @@ public: } } double nyquist = m_parameters.sampleRate / 2.0; - int top = binForFrequency(16000.0); - if (top >= n) top = n-1; double f1 = nyquist; double f2 = nyquist; bool inPercussive = false; - for (int i = top; i > 0; --i) { + for (int i = n - 1; i > 0; --i) { if (m_numeric[i] == 1) { // percussive if (!inPercussive) { inPercussive = true; @@ -103,7 +101,6 @@ public: protected: Parameters m_parameters; - int m_binCount; std::vector m_numeric; MovingMedian m_classFilter; diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 0d42995..4849dbc 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -43,12 +43,20 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, m_prevOuthop(1), m_draining(false) { + 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, - m_parameters.sampleRate); + classificationBins, m_parameters.sampleRate); + BinClassifier::Parameters classifierParameters - (m_guideConfiguration.classificationFftSize / 2 + 1, - 9, 1, 10, 2.0, 2.0, 1.0e-7); + (classificationBins, 9, 1, 10, 2.0, 2.0, 1.0e-7); int inRingBufferSize = m_guideConfiguration.longestFftSize * 2; int outRingBufferSize = m_guideConfiguration.longestFftSize * 16; From 0bc0d7deb046317397116e627856a749258b07a4 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 7 Jun 2022 11:12:39 +0100 Subject: [PATCH 069/184] Avoid compiler warning --- src/common/SampleFilter.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/SampleFilter.h b/src/common/SampleFilter.h index 7dea368..74f197b 100644 --- a/src/common/SampleFilter.h +++ b/src/common/SampleFilter.h @@ -33,6 +33,7 @@ template class SampleFilter { public: + virtual ~SampleFilter() { } virtual int getSize() const = 0; virtual void push(T) = 0; virtual T get() const = 0; From b8b650cd5cd7209ce1bb71afff76026518029037 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 7 Jun 2022 11:19:29 +0100 Subject: [PATCH 070/184] Show fps as int (avoiding scientific notation) --- main/main.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/main/main.cpp b/main/main.cpp index 7e576e1..265a52d 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -927,7 +927,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(); From 07b339df1315d638beae4786774e52c17145c619 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 7 Jun 2022 12:12:06 +0100 Subject: [PATCH 071/184] Try a peak p-factor of 1 for the previous peaks --- src/finer/PhaseAdvance.h | 14 ++++++++++---- src/finer/R3StretcherImpl.cpp | 5 ++--- src/finer/R3StretcherImpl.h | 6 ++++-- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/finer/PhaseAdvance.h b/src/finer/PhaseAdvance.h index 92e75ec..3789b79 100644 --- a/src/finer/PhaseAdvance.h +++ b/src/finer/PhaseAdvance.h @@ -86,6 +86,7 @@ public: 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, @@ -132,10 +133,15 @@ public: int endBin = binForFrequency(band.f1); if (startBin > highest || endBin < lowest) continue; int count = endBin - startBin + 1; - m_peakPicker.findNearestAndNextPeaks(mag[c], startBin, count, + 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) { @@ -218,9 +224,9 @@ public: } } - int **tmp = m_prevPeaks; - m_prevPeaks = m_currentPeaks; - m_currentPeaks = tmp; +// int **tmp = m_prevPeaks; +// m_prevPeaks = m_currentPeaks; +// m_currentPeaks = tmp; } protected: diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 4849dbc..d2b078b 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -121,7 +121,6 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, WindowType R3StretcherImpl::ScaleData::analysisWindowShape(int fftSize) { -//!!! return HannWindow; if (fftSize == 4096) return HannWindow; else return NiemitaloForwardWindow; } @@ -135,7 +134,6 @@ R3StretcherImpl::ScaleData::analysisWindowLength(int fftSize) WindowType R3StretcherImpl::ScaleData::synthesisWindowShape(int fftSize) { -//!!! return HannWindow; if (fftSize == 4096) return HannWindow; else return NiemitaloReverseWindow; } @@ -143,7 +141,6 @@ R3StretcherImpl::ScaleData::synthesisWindowShape(int fftSize) int R3StretcherImpl::ScaleData::synthesisWindowLength(int fftSize) { -//!!! return fftSize/2; if (fftSize == 4096) return fftSize/2; else return fftSize; } @@ -405,6 +402,7 @@ R3StretcherImpl::consume() 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(); } @@ -412,6 +410,7 @@ R3StretcherImpl::consume() (m_channelAssembly.outPhase.data(), m_channelAssembly.mag.data(), m_channelAssembly.phase.data(), + m_channelAssembly.prevMag.data(), m_guideConfiguration, m_channelAssembly.guidance.data(), m_prevInhop, diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 80e9bc9..3213d7e 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -218,14 +218,16 @@ protected: // 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), - guidance(channels, nullptr), outPhase(channels, nullptr), - mixdown(channels, nullptr), resampled(channels, nullptr) { } + prevMag(channels, nullptr), guidance(channels, nullptr), + outPhase(channels, nullptr), mixdown(channels, nullptr), + resampled(channels, nullptr) { } }; struct ScaleData { From 36f993b9a66bb677ae18f0beb69e218e4d75a133 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 7 Jun 2022 14:13:24 +0100 Subject: [PATCH 072/184] Reduce p level for trough picker. The behaviour here is not what I'd hoped --- src/finer/Guide.h | 2 ++ src/finer/R3StretcherImpl.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/finer/Guide.h b/src/finer/Guide.h index 1541198..f2fa0d9 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -223,6 +223,8 @@ public: 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; diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index d2b078b..8deef82 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -654,7 +654,7 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) cd->nextSegmentation = cd->segmenter->segment(cd->nextClassification.data()); m_troughPicker.findNearestAndNextPeaks - (classifyScale->mag.data(), 3, nullptr, + (classifyScale->mag.data(), 1, nullptr, classifyScale->troughs.data()); double instantaneousRatio = double(prevOuthop) / double(prevInhop); From 95a1d6df2530cd183cbdae904065eb2baf4917bc Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 8 Jun 2022 09:57:12 +0100 Subject: [PATCH 073/184] Start to bring in unit tests --- .hgignore | 4 + meson.build | 57 +++ src/finer/Guide.h | 45 +- src/finer/R3StretcherImpl.cpp | 2 +- src/test/TestAllocators.cpp | 104 +++++ src/test/TestFFT.cpp | 723 ++++++++++++++++++++++++++++++ src/test/TestResampler.cpp | 218 +++++++++ src/test/TestVectorOps.cpp | 249 ++++++++++ src/test/TestVectorOpsComplex.cpp | 134 ++++++ src/test/test.cpp | 26 ++ 10 files changed, 1556 insertions(+), 6 deletions(-) create mode 100644 src/test/TestAllocators.cpp create mode 100644 src/test/TestFFT.cpp create mode 100644 src/test/TestResampler.cpp create mode 100644 src/test/TestVectorOps.cpp create mode 100644 src/test/TestVectorOpsComplex.cpp create mode 100644 src/test/test.cpp 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/meson.build b/meson.build index f4b05f8..cce1856 100644 --- a/meson.build +++ b/meson.build @@ -83,6 +83,15 @@ 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/test.cpp', +] + general_include_dirs = [ 'rubberband', 'src', @@ -100,6 +109,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_unit_test_framework', 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) @@ -312,6 +322,15 @@ if not sndfile_dep.found() endif have_sndfile = sndfile_dep.found() +if not boost_unit_test_dep.found() + boost_unit_test_dep = cpp.find_library('boost_unit_test_framework', + dirs: get_option('extra_lib_dirs'), + has_headers: ['boost/test/unit_test.hpp'], + header_args: extra_include_args, + required: false) +endif +have_boost_unit_test = boost_unit_test_dep.found() + # General platform and compiler expectations @@ -430,6 +449,7 @@ if cpp.get_id() == 'msvc' 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' @@ -438,6 +458,7 @@ else rubberband_lv2_name = 'lv2-rubberband' rubberband_vamp_name = 'vamp-rubberband' rubberband_jni_name = 'rubberband-jni' + unit_tests_name = 'tests' endif rubberband_objlib = static_library( @@ -686,6 +707,42 @@ else message('Not building command-line utility: libsndfile dependency not found') 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 ]) +else + target_summary += { 'Unit tests': false } + message('Not building unit tests: boost_unit_test_framework dependency not found') +endif + pkg.generate( name: 'rubberband', description: 'Audio time-stretching and pitch-shifting library', diff --git a/src/finer/Guide.h b/src/finer/Guide.h index f2fa0d9..5524404 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -214,17 +214,38 @@ public: guidance.phaseReset.f1 = std::max(segmentation.residualAbove, nextSegmentation.residualAbove); } - +/* double higher = snapToTrough(m_defaultHigher, troughs); if (higher > m_maxHigher) higher = m_maxHigher; double lower = snapToTrough(m_defaultLower, troughs); if (lower > m_maxLower) lower = m_maxLower; - +*/ +/* + double prevHigher = guidance.fftBands[1].f1; + double higher = snapToTrough(prevHigher, troughs, magnitudes); + if (higher < m_minHigher || higher > m_maxHigher) { + higher = snapToTrough(m_defaultHigher, troughs, magnitudes); + if (higher < m_minHigher || higher > m_maxHigher) { + higher = prevHigher; + } + } +*/ + double higher = m_defaultHigher; + + double prevLower = guidance.fftBands[0].f1; + double lower = snapToTrough(prevLower, troughs, magnitudes); + if (lower < m_minLower || lower > m_maxLower) { + lower = snapToTrough(m_defaultLower, troughs, magnitudes); + if (lower < m_minLower || lower > m_maxLower) { + lower = prevLower; + } + } + guidance.fftBands[0].f0 = 0.0; guidance.fftBands[0].f1 = lower; -// std::cout << "x:" << lower << std::endl; + std::cout << "x:" << lower << std::endl; guidance.fftBands[1].f0 = lower; guidance.fftBands[1].f1 = higher; @@ -322,8 +343,22 @@ protected: return (here > 10.e-3 && here > there * 1.4); } - double snapToTrough(double f, const int *const troughs) const { - return frequencyForBin(troughs[binForFrequency(f)]); + double snapToTrough(double f, + const int *const troughs, + const double *const magnitudes) const { +// return frequencyForBin(troughs[binForFrequency(f)]); + int bin = binForFrequency(f); + int snapped = troughs[bin]; + double sf = frequencyForBin(snapped); + std::cout << "snapToTrough: " << f << " -> bin " << bin << " -> snapped " << snapped << " -> " << sf << std::endl; + for (int i = -3; i <= 3; ++i) { + if (i == 0) std::cout << "["; + std::cout << magnitudes[bin + i]; + if (i == 0) std::cout << "]"; + std::cout << " "; + } + std::cout << std::endl; + return sf; } double betaFor(double f, double ratio) const { diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 8deef82..d2b078b 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -654,7 +654,7 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) cd->nextSegmentation = cd->segmenter->segment(cd->nextClassification.data()); m_troughPicker.findNearestAndNextPeaks - (classifyScale->mag.data(), 1, nullptr, + (classifyScale->mag.data(), 3, nullptr, classifyScale->troughs.data()); double instantaneousRatio = double(prevOuthop) / double(prevInhop); diff --git a/src/test/TestAllocators.cpp b/src/test/TestAllocators.cpp new file mode 100644 index 0000000..32f547e --- /dev/null +++ b/src/test/TestAllocators.cpp @@ -0,0 +1,104 @@ +/* -*- 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. +*/ + +#define BOOST_TEST_DYN_LINK +#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/TestFFT.cpp b/src/test/TestFFT.cpp new file mode 100644 index 0000000..78b5df2 --- /dev/null +++ b/src/test/TestFFT.cpp @@ -0,0 +1,723 @@ +/* -*- 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. +*/ + +#define BOOST_TEST_DYN_LINK +#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..774c2b5 --- /dev/null +++ b/src/test/TestResampler.cpp @@ -0,0 +1,218 @@ +/* -*- 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. +*/ + +#define BOOST_TEST_DYN_LINK +#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/TestVectorOps.cpp b/src/test/TestVectorOps.cpp new file mode 100644 index 0000000..615a89f --- /dev/null +++ b/src/test/TestVectorOps.cpp @@ -0,0 +1,249 @@ +/* -*- 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. +*/ + +#define BOOST_TEST_DYN_LINK +#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..dd73cd8 --- /dev/null +++ b/src/test/TestVectorOpsComplex.cpp @@ -0,0 +1,134 @@ +/* -*- 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. +*/ + +#define BOOST_TEST_DYN_LINK +#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/test/test.cpp b/src/test/test.cpp new file mode 100644 index 0000000..e5fda23 --- /dev/null +++ b/src/test/test.cpp @@ -0,0 +1,26 @@ +/* -*- 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. +*/ + +#define BOOST_TEST_MODULE RubberBand +#define BOOST_TEST_DYN_LINK +#include From 0b8b0742c1673f5912da59fcbe25b1b08ef3bff7 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 8 Jun 2022 10:35:51 +0100 Subject: [PATCH 074/184] A few signal-bits tests --- meson.build | 5 +- src/common/MovingMedian.h | 18 +++-- src/finer/BinClassifier.h | 2 +- src/finer/BinSegmenter.h | 2 +- src/test/TestSignalBits.cpp | 137 ++++++++++++++++++++++++++++++++++++ 5 files changed, 155 insertions(+), 9 deletions(-) create mode 100644 src/test/TestSignalBits.cpp diff --git a/meson.build b/meson.build index cce1856..b0eec3b 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,7 @@ project( 'Rubber Band Library', 'c', 'cpp', - version: '2.0.2', + version: '3.0.0', license: 'GPL-2.0-or-later', default_options: [ 'cpp_std=c++11', @@ -89,6 +89,7 @@ unit_test_sources = [ 'src/test/TestResampler.cpp', 'src/test/TestVectorOpsComplex.cpp', 'src/test/TestVectorOps.cpp', + 'src/test/TestSignalBits.cpp', 'src/test/test.cpp', ] @@ -738,6 +739,8 @@ if have_boost_unit_test 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') diff --git a/src/common/MovingMedian.h b/src/common/MovingMedian.h index 8d742ab..5c5af5b 100644 --- a/src/common/MovingMedian.h +++ b/src/common/MovingMedian.h @@ -205,15 +205,15 @@ public: } // Convenience function that applies a given filter to an array - // in-place. Array must have length equal to getSize(). Modifies - // both the filter and the array. + // in-place. Array has length n. Modifies both the filter and the + // array. // - static void filter(MovingMedian &mm, T *v) { - int n = mm.getSize(); - int lag = n / 2; + static void filter(MovingMedian &mm, T *v, int n) { + int fn = mm.getSize(); + int lag = fn / 2; mm.reset(); for (int i = 0; i < lag; ++i) { - mm.push(v[i]); + if (i < n) mm.push(v[i]); } for (int i = lag; i < n; ++i) { mm.push(v[i]); @@ -224,6 +224,12 @@ public: v[i-lag] = mm.get(); } } + + // As above but with a vector argument + // + static void filter(MovingMedian &mm, std::vector &v) { + filter(mm, v.data(), v.size()); + } private: MovingMedianStack m_mm; diff --git a/src/finer/BinClassifier.h b/src/finer/BinClassifier.h index 94b1fa6..350db7c 100644 --- a/src/finer/BinClassifier.h +++ b/src/finer/BinClassifier.h @@ -109,7 +109,7 @@ public: } v_copy(m_vf, mag, n); - MovingMedian::filter(*m_vFilter, m_vf); + MovingMedian::filter(*m_vFilter, m_vf, n); if (m_parameters.horizontalFilterLag > 0) { double *lagged = m_vfQueue.readOne(); diff --git a/src/finer/BinSegmenter.h b/src/finer/BinSegmenter.h index 00fb1e4..31ce08a 100644 --- a/src/finer/BinSegmenter.h +++ b/src/finer/BinSegmenter.h @@ -70,7 +70,7 @@ public: m_numeric[i] = 2; break; } } - MovingMedian::filter(m_classFilter, m_numeric.data()); + MovingMedian::filter(m_classFilter, m_numeric.data(), m_numeric.size()); double f0 = 0.0; for (int i = 1; i < n; ++i) { if (m_numeric[i] != 1) { diff --git a/src/test/TestSignalBits.cpp b/src/test/TestSignalBits.cpp new file mode 100644 index 0000000..58b8c54 --- /dev/null +++ b/src/test/TestSignalBits.cpp @@ -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. +*/ + +#define BOOST_TEST_DYN_LINK +#include + +#include "../common/MovingMedian.h" +#include "../finer/Peak.h" + +using namespace RubberBand; +using namespace std; + +BOOST_AUTO_TEST_SUITE(TestSignalBits) + +#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); \ + } + +#define COMPARE_INT_N(a, b, n) \ + for (int cmp_i = 0; cmp_i < n; ++cmp_i) { \ + BOOST_CHECK_EQUAL(a[cmp_i], b[cmp_i]); \ + } + +BOOST_AUTO_TEST_CASE(moving_median_simple_3) +{ + MovingMedian mm(3); + vector arr { 1.0, 2.0, 3.0 }; + vector expected { 2.0, 2.0, 3.0 }; + MovingMedian::filter(mm, arr); + for (int i = 0; i < arr.size(); ++i) { + cerr << arr[i] << endl; + } + COMPARE_N(arr, expected, 3); +} + +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); + for (int i = 0; i < arr.size(); ++i) { + cerr << arr[i] << endl; + } + COMPARE_N(arr, expected, 4); +} + +BOOST_AUTO_TEST_CASE(moving_median_simple_5_4) +{ + MovingMedian mm(5); + vector arr { 1.2, 0.6, 1.0e-6, 1.0e-6 }; + vector expected { 0.6, 1.2, 1.2, 0.6 }; + MovingMedian::filter(mm, arr); + for (int i = 0; i < arr.size(); ++i) { + cerr << arr[i] << endl; + } + COMPARE_N(arr, expected, 4); +} + +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); + for (int i = 0; i < arr.size(); ++i) { + cerr << arr[i] << endl; + } + COMPARE_N(arr, expected, 4); +} + +BOOST_AUTO_TEST_CASE(moving_median_n_1) +{ + MovingMedian mm(6); + vector arr { 1.0 }; + vector expected = arr; + MovingMedian::filter(mm, arr); + for (int i = 0; i < arr.size(); ++i) { + cerr << arr[i] << endl; + } + COMPARE_N(arr, expected, 1); +} + +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); + COMPARE_INT_N(out, expected, 1); +} + +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); + COMPARE_INT_N(out, expected, 5); +} + +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); + COMPARE_INT_N(out, expected, 12); +} + +BOOST_AUTO_TEST_SUITE_END() + From 75b1c9abb52b64849261a50b530eb85857ce9b95 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 8 Jun 2022 11:39:16 +0100 Subject: [PATCH 075/184] Fixes to MovingMedian edge-cases and related tests --- src/common/MovingMedian.h | 12 ++++++++--- src/test/TestSignalBits.cpp | 41 +++++++++++++++++++------------------ 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/common/MovingMedian.h b/src/common/MovingMedian.h index 5c5af5b..e8d40ed 100644 --- a/src/common/MovingMedian.h +++ b/src/common/MovingMedian.h @@ -212,14 +212,20 @@ public: int fn = mm.getSize(); int lag = fn / 2; mm.reset(); - for (int i = 0; i < lag; ++i) { + int i = 0; + for (; i < lag; ++i) { if (i < n) mm.push(v[i]); } - for (int i = lag; i < n; ++i) { + for (; i < n; ++i) { mm.push(v[i]); v[i-lag] = mm.get(); } - for (int i = n; i < n + lag; ++i) { + for (; i < lag; ++i) { + // just for the unusual case where lag > n + mm.push(T()); + (void)mm.get(); + } + for (; i < n + lag; ++i) { mm.push(T()); v[i-lag] = mm.get(); } diff --git a/src/test/TestSignalBits.cpp b/src/test/TestSignalBits.cpp index 58b8c54..5610a79 100644 --- a/src/test/TestSignalBits.cpp +++ b/src/test/TestSignalBits.cpp @@ -42,15 +42,19 @@ BOOST_AUTO_TEST_SUITE(TestSignalBits) BOOST_CHECK_EQUAL(a[cmp_i], b[cmp_i]); \ } +// NB our moving median has different lag behaviour from bsq - we +// begin padded with zeros, while bsq begins with an empty vector. The +// bsq behaviour is imho more correct, and this really shows up in the +// n_1 case below (where the correct answer is surely {1.0} rather +// than {0.0}) but ours is not wholly wrong, more efficient, "usually +// fine" + BOOST_AUTO_TEST_CASE(moving_median_simple_3) { MovingMedian mm(3); vector arr { 1.0, 2.0, 3.0 }; - vector expected { 2.0, 2.0, 3.0 }; + vector expected { 1.0, 2.0, 2.0 }; MovingMedian::filter(mm, arr); - for (int i = 0; i < arr.size(); ++i) { - cerr << arr[i] << endl; - } COMPARE_N(arr, expected, 3); } @@ -58,23 +62,26 @@ 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 }; + vector expected { 2.0, 3.0, 3.0, 3.0 }; + MovingMedian::filter(mm, arr); + COMPARE_N(arr, expected, 4); +} + +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); - for (int i = 0; i < arr.size(); ++i) { - cerr << arr[i] << endl; - } COMPARE_N(arr, expected, 4); } BOOST_AUTO_TEST_CASE(moving_median_simple_5_4) { MovingMedian mm(5); - vector arr { 1.2, 0.6, 1.0e-6, 1.0e-6 }; - vector expected { 0.6, 1.2, 1.2, 0.6 }; + vector arr { 1.2, 0.6, 1.0e-6, 1.0 }; + vector expected { 1.0e-6, 0.6, 0.6, 1.0e-6 }; MovingMedian::filter(mm, arr); - for (int i = 0; i < arr.size(); ++i) { - cerr << arr[i] << endl; - } COMPARE_N(arr, expected, 4); } @@ -84,9 +91,6 @@ BOOST_AUTO_TEST_CASE(moving_median_order_1) vector arr { 1.2, 0.6, 1.0e-6, 1.0e-6 }; vector expected = arr; MovingMedian::filter(mm, arr); - for (int i = 0; i < arr.size(); ++i) { - cerr << arr[i] << endl; - } COMPARE_N(arr, expected, 4); } @@ -94,11 +98,8 @@ BOOST_AUTO_TEST_CASE(moving_median_n_1) { MovingMedian mm(6); vector arr { 1.0 }; - vector expected = arr; + vector expected { 0.0 }; MovingMedian::filter(mm, arr); - for (int i = 0; i < arr.size(); ++i) { - cerr << arr[i] << endl; - } COMPARE_N(arr, expected, 1); } From 625745732074f1731cba82f9eee72433fdf0318f Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 9 Jun 2022 09:03:20 +0100 Subject: [PATCH 076/184] Small correction to Peak; expand and fix tests --- src/finer/Peak.h | 4 +- src/test/TestSignalBits.cpp | 122 ++++++++++++++++++++++++++++++------ 2 files changed, 106 insertions(+), 20 deletions(-) diff --git a/src/finer/Peak.h b/src/finer/Peak.h index 87a224f..7ede11c 100644 --- a/src/finer/Peak.h +++ b/src/finer/Peak.h @@ -98,9 +98,11 @@ public: int np = i; if (j < nPeaks) { np = m_locations[j]; + } else if (nPeaks > 0) { + np = m_locations[nPeaks-1]; } if (next) { - if (pp == i) { + if (pp == i || j >= nPeaks) { next[i] = i; } else { next[i] = np; diff --git a/src/test/TestSignalBits.cpp b/src/test/TestSignalBits.cpp index 5610a79..73dc467 100644 --- a/src/test/TestSignalBits.cpp +++ b/src/test/TestSignalBits.cpp @@ -29,19 +29,10 @@ using namespace RubberBand; using namespace std; +namespace tt = boost::test_tools; BOOST_AUTO_TEST_SUITE(TestSignalBits) -#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); \ - } - -#define COMPARE_INT_N(a, b, n) \ - for (int cmp_i = 0; cmp_i < n; ++cmp_i) { \ - BOOST_CHECK_EQUAL(a[cmp_i], b[cmp_i]); \ - } - // NB our moving median has different lag behaviour from bsq - we // begin padded with zeros, while bsq begins with an empty vector. The // bsq behaviour is imho more correct, and this really shows up in the @@ -55,7 +46,7 @@ BOOST_AUTO_TEST_CASE(moving_median_simple_3) vector arr { 1.0, 2.0, 3.0 }; vector expected { 1.0, 2.0, 2.0 }; MovingMedian::filter(mm, arr); - COMPARE_N(arr, expected, 3); + BOOST_TEST(arr == expected, tt::per_element()); } BOOST_AUTO_TEST_CASE(moving_median_simple_4) @@ -64,7 +55,7 @@ BOOST_AUTO_TEST_CASE(moving_median_simple_4) vector arr { 1.0, 2.0, 3.0, 4.0 }; vector expected { 2.0, 3.0, 3.0, 3.0 }; MovingMedian::filter(mm, arr); - COMPARE_N(arr, expected, 4); + BOOST_TEST(arr == expected, tt::per_element()); } BOOST_AUTO_TEST_CASE(moving_median_simple_3_4) @@ -73,7 +64,7 @@ BOOST_AUTO_TEST_CASE(moving_median_simple_3_4) 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); - COMPARE_N(arr, expected, 4); + BOOST_TEST(arr == expected, tt::per_element()); } BOOST_AUTO_TEST_CASE(moving_median_simple_5_4) @@ -82,7 +73,7 @@ BOOST_AUTO_TEST_CASE(moving_median_simple_5_4) vector arr { 1.2, 0.6, 1.0e-6, 1.0 }; vector expected { 1.0e-6, 0.6, 0.6, 1.0e-6 }; MovingMedian::filter(mm, arr); - COMPARE_N(arr, expected, 4); + BOOST_TEST(arr == expected, tt::per_element()); } BOOST_AUTO_TEST_CASE(moving_median_order_1) @@ -91,7 +82,7 @@ BOOST_AUTO_TEST_CASE(moving_median_order_1) vector arr { 1.2, 0.6, 1.0e-6, 1.0e-6 }; vector expected = arr; MovingMedian::filter(mm, arr); - COMPARE_N(arr, expected, 4); + BOOST_TEST(arr == expected, tt::per_element()); } BOOST_AUTO_TEST_CASE(moving_median_n_1) @@ -100,7 +91,7 @@ BOOST_AUTO_TEST_CASE(moving_median_n_1) vector arr { 1.0 }; vector expected { 0.0 }; MovingMedian::filter(mm, arr); - COMPARE_N(arr, expected, 1); + BOOST_TEST(arr == expected, tt::per_element()); } BOOST_AUTO_TEST_CASE(peakpick_nearest_2_1) @@ -110,7 +101,7 @@ BOOST_AUTO_TEST_CASE(peakpick_nearest_2_1) vector out(1); vector expected { 0 }; pp.findNearestAndNextPeaks(in.data(), 2, out.data(), nullptr); - COMPARE_INT_N(out, expected, 1); + BOOST_TEST(out == expected, tt::per_element()); } BOOST_AUTO_TEST_CASE(peakpick_nearest_2_5) @@ -120,7 +111,7 @@ BOOST_AUTO_TEST_CASE(peakpick_nearest_2_5) vector out(5); vector expected { 3, 3, 3, 3, 3 }; pp.findNearestAndNextPeaks(in.data(), 2, out.data(), nullptr); - COMPARE_INT_N(out, expected, 5); + BOOST_TEST(out == expected, tt::per_element()); } BOOST_AUTO_TEST_CASE(peakpick_nearest_2_12) @@ -131,7 +122,100 @@ BOOST_AUTO_TEST_CASE(peakpick_nearest_2_12) 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); - COMPARE_INT_N(out, expected, 12); + 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() From 022bb6d40ceda3ae5559d350e49b959c15c75c4e Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 9 Jun 2022 14:16:40 +0100 Subject: [PATCH 077/184] Fix segmentation logic --- src/finer/BinSegmenter.h | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/finer/BinSegmenter.h b/src/finer/BinSegmenter.h index 31ce08a..cc86343 100644 --- a/src/finer/BinSegmenter.h +++ b/src/finer/BinSegmenter.h @@ -83,19 +83,30 @@ public: double f2 = nyquist; bool inPercussive = false; for (int i = n - 1; i > 0; --i) { - if (m_numeric[i] == 1) { // percussive - if (!inPercussive) { + int c = m_numeric[i]; + if (!inPercussive) { + if (c == 2) { // residual/silent + continue; + } else if (c == 1) { // percussive inPercussive = true; f2 = frequencyForBin(i); - continue; + } else { // harmonic + f1 = f2 = frequencyForBin(i); + break; } - } else if (m_numeric[i] == 0) { // harmonic - if (inPercussive) { + } else { // inPercussive + if (c != 1) { // non-percussive f1 = frequencyForBin(i); + break; } - break; // always when harmonic reached } } + if (f1 == nyquist && f2 < nyquist) { + f1 = 0.0; + } + +// std::cout << "f0 = " << f0 << ", f1 = " << f1 << ", f2 = " << f2 << std::endl; + return Segmentation(f0, f1, f2); } From 70a7b6d6883b7d83806d988888c271e8c3c87c1c Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 9 Jun 2022 14:16:49 +0100 Subject: [PATCH 078/184] Some (temporary) debug etc --- src/finer/BinSegmenter.h | 16 ++++++++++++++-- src/finer/R3StretcherImpl.cpp | 24 ++++++++++++++++++++++++ src/finer/R3StretcherImpl.h | 10 ++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/finer/BinSegmenter.h b/src/finer/BinSegmenter.h index cc86343..dc4dee0 100644 --- a/src/finer/BinSegmenter.h +++ b/src/finer/BinSegmenter.h @@ -54,7 +54,7 @@ public: BinSegmenter(Parameters parameters) : m_parameters(parameters), m_numeric(m_parameters.binCount, 0), - m_classFilter(16) + m_classFilter(15) { } @@ -70,7 +70,19 @@ public: m_numeric[i] = 2; break; } } - MovingMedian::filter(m_classFilter, m_numeric.data(), m_numeric.size()); + MovingMedian::filter(m_classFilter, m_numeric); +/* + std::cout << "c:"; + for (int i = 0; i < n; ++i) { + if (i > 0) std::cout << ","; + if (m_numeric[i] == 1) { + std::cout << "1"; + } else { + std::cout << "0"; + } + } + std::cout << std::endl; +*/ double f0 = 0.0; for (int i = 1; i < n; ++i) { if (m_numeric[i] != 1) { diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index d2b078b..2742425 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -653,6 +653,30 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) 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); + int pab = binForFrequency(pa, classify); + int rab = binForFrequency(ra, classify); + 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; + } +*/ + m_troughPicker.findNearestAndNextPeaks (classifyScale->mag.data(), 3, nullptr, classifyScale->troughs.data()); diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 3213d7e..fdcec77 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -283,6 +283,16 @@ protected: static void logCout(const std::string &message) { std::cout << "RubberBandStretcher: " << message << std::endl; } + + //!!! dupes + int binForFrequency(double f, int fftSize) const { + return int(round(f * double(fftSize) / + m_parameters.sampleRate)); + } + double frequencyForBin(int b, int fftSize) const { + return (double(b) * m_parameters.sampleRate) + / double(fftSize); + } }; } From ab284f0047f7aa57629c89435de9549daed32a69 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 9 Jun 2022 14:29:51 +0100 Subject: [PATCH 079/184] Rather than using trough picker, make the division frequencies drift downhill --- src/finer/Guide.h | 57 ++++++++++++++++++++++++----------- src/finer/R3StretcherImpl.cpp | 22 +++++--------- src/finer/R3StretcherImpl.h | 3 -- 3 files changed, 47 insertions(+), 35 deletions(-) diff --git a/src/finer/Guide.h b/src/finer/Guide.h index 5524404..e3239a9 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -137,15 +137,14 @@ public: return m_configuration; } - void calculate(double ratio, - const double *const magnitudes, - const int *const troughs, - const double *const prevMagnitudes, - const BinSegmenter::Segmentation &segmentation, - const BinSegmenter::Segmentation &prevSegmentation, - const BinSegmenter::Segmentation &nextSegmentation, - bool specialCaseUnity, - Guidance &guidance) const { + void updateGuidance(double ratio, + const double *const magnitudes, + const double *const prevMagnitudes, + const BinSegmenter::Segmentation &segmentation, + const BinSegmenter::Segmentation &prevSegmentation, + const BinSegmenter::Segmentation &nextSegmentation, + bool specialCaseUnity, + Guidance &guidance) const { guidance.kick.present = false; guidance.lowPercussive.present = false; @@ -221,8 +220,20 @@ public: double lower = snapToTrough(m_defaultLower, troughs); if (lower > m_maxLower) lower = m_maxLower; */ -/* + 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; + } + + +/* double higher = snapToTrough(prevHigher, troughs, magnitudes); if (higher < m_minHigher || higher > m_maxHigher) { higher = snapToTrough(m_defaultHigher, troughs, magnitudes); @@ -231,17 +242,16 @@ public: } } */ + /* double higher = m_defaultHigher; - double prevLower = guidance.fftBands[0].f1; - double lower = snapToTrough(prevLower, troughs, magnitudes); if (lower < m_minLower || lower > m_maxLower) { lower = snapToTrough(m_defaultLower, troughs, magnitudes); if (lower < m_minLower || lower > m_maxLower) { lower = prevLower; } } - + */ guidance.fftBands[0].f0 = 0.0; guidance.fftBands[0].f1 = lower; @@ -343,13 +353,22 @@ protected: return (here > 10.e-3 && here > there * 1.4); } - double snapToTrough(double f, - const int *const troughs, - const double *const magnitudes) const { + double descendToValley(double f, const double *const magnitudes) const { // return frequencyForBin(troughs[binForFrequency(f)]); - int bin = binForFrequency(f); + int b = binForFrequency(f); + + for (int i = 0; i < 3; ++i) { + if (magnitudes[b+1] < magnitudes[b]) { + ++b; + } else if (magnitudes[b-1] < magnitudes[b]) { + --b; + } else { + break; + } + } + +/* int snapped = troughs[bin]; - double sf = frequencyForBin(snapped); std::cout << "snapToTrough: " << f << " -> bin " << bin << " -> snapped " << snapped << " -> " << sf << std::endl; for (int i = -3; i <= 3; ++i) { if (i == 0) std::cout << "["; @@ -358,6 +377,8 @@ protected: std::cout << " "; } std::cout << std::endl; +*/ + double sf = frequencyForBin(b); return sf; } diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 2742425..f10483c 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -38,7 +38,6 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, m_guide(Guide::Parameters(m_parameters.sampleRate, parameters.logger)), m_guideConfiguration(m_guide.getConfiguration()), m_channelAssembly(m_parameters.channels), - m_troughPicker(m_guideConfiguration.classificationFftSize / 2 + 1), m_inhop(1), m_prevOuthop(1), m_draining(false) @@ -677,22 +676,17 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) } */ - m_troughPicker.findNearestAndNextPeaks - (classifyScale->mag.data(), 3, nullptr, - classifyScale->troughs.data()); - double instantaneousRatio = double(prevOuthop) / double(prevInhop); bool specialCaseUnity = true; - m_guide.calculate(instantaneousRatio, - classifyScale->mag.data(), - classifyScale->troughs.data(), - classifyScale->prevMag.data(), - cd->segmentation, - cd->prevSegmentation, - cd->nextSegmentation, - specialCaseUnity, - cd->guidance); + m_guide.updateGuidance(instantaneousRatio, + classifyScale->mag.data(), + classifyScale->prevMag.data(), + cd->segmentation, + cd->prevSegmentation, + cd->nextSegmentation, + specialCaseUnity, + cd->guidance); } void diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index fdcec77..ab6db5f 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -109,7 +109,6 @@ protected: FixedVector mag; FixedVector phase; FixedVector advancedPhase; - FixedVector troughs; FixedVector prevMag; FixedVector accumulator; @@ -122,7 +121,6 @@ protected: mag(bufSize, 0.f), phase(bufSize, 0.f), advancedPhase(bufSize, 0.f), - troughs(bufSize, 0), prevMag(bufSize, 0.f), accumulator(_longestFftSize, 0.f) { } @@ -261,7 +259,6 @@ protected: Guide m_guide; Guide::Configuration m_guideConfiguration; ChannelAssembly m_channelAssembly; - Peak> m_troughPicker; std::unique_ptr m_calculator; std::unique_ptr m_resampler; std::atomic m_inhop; From 4457247749071f937a6886fbd0b714e49f057f5e Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 9 Jun 2022 14:56:20 +0100 Subject: [PATCH 080/184] Synchronise frequency channel cutoffs across channels. Not the most elegant way to do this I think --- src/finer/Guide.h | 5 +++-- src/finer/R3StretcherImpl.cpp | 33 +++++++++++++++++++++++++++++++-- src/finer/R3StretcherImpl.h | 4 +++- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/finer/Guide.h b/src/finer/Guide.h index e3239a9..a779da0 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -140,6 +140,7 @@ public: void updateGuidance(double ratio, const double *const magnitudes, const double *const prevMagnitudes, + const double *const channelMixedMagnitudes, const BinSegmenter::Segmentation &segmentation, const BinSegmenter::Segmentation &prevSegmentation, const BinSegmenter::Segmentation &nextSegmentation, @@ -221,13 +222,13 @@ public: if (lower > m_maxLower) lower = m_maxLower; */ double prevLower = guidance.fftBands[0].f1; - double lower = descendToValley(prevLower, magnitudes); + double lower = descendToValley(prevLower, channelMixedMagnitudes); if (lower > m_maxLower || lower < m_minLower) { lower = m_defaultLower; } double prevHigher = guidance.fftBands[1].f1; - double higher = descendToValley(prevHigher, magnitudes); + double higher = descendToValley(prevHigher, channelMixedMagnitudes); if (higher > m_maxHigher || higher < m_minHigher) { higher = m_defaultHigher; } diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index f10483c..22580ce 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -38,6 +38,7 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, m_guide(Guide::Parameters(m_parameters.sampleRate, parameters.logger)), m_guideConfiguration(m_guide.getConfiguration()), m_channelAssembly(m_parameters.channels), + m_mixedClassifyMags(m_guideConfiguration.classificationFftSize / 2 + 1, 0.0), m_inhop(1), m_prevOuthop(1), m_draining(false) @@ -389,7 +390,24 @@ R3StretcherImpl::consume() // Analysis for (int c = 0; c < channels; ++c) { - analyseChannel(c, inhop, m_prevInhop, m_prevOuthop); + analyseChannel(c, inhop, m_prevInhop); + } + + for (int c = 0; c < channels; ++c) { + auto &classifyScale = + m_channelData.at(c)-> + scales.at(m_guideConfiguration.classificationFftSize); + if (c == 0) { + v_copy(m_mixedClassifyMags.data(), classifyScale->mag.data(), + classifyScale->mag.size()); + } else { + v_add(m_mixedClassifyMags.data(), classifyScale->mag.data(), + classifyScale->mag.size()); + } + } + + for (int c = 0; c < channels; ++c) { + guideChannel(c, m_prevInhop, m_prevOuthop); } // Phase update. This is synchronised across all channels @@ -465,7 +483,7 @@ R3StretcherImpl::consume() } void -R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) +R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop) { int longest = m_guideConfiguration.longestFftSize; int classify = m_guideConfiguration.classificationFftSize; @@ -651,7 +669,17 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) cd->prevSegmentation = cd->segmentation; cd->segmentation = cd->nextSegmentation; cd->nextSegmentation = cd->segmenter->segment(cd->nextClassification.data()); +} +void +R3StretcherImpl::guideChannel(int c, int prevInhop, int prevOuthop) +{ + auto &cd = m_channelData.at(c); + int classify = m_guideConfiguration.classificationFftSize; + auto &classifyScale = cd->scales.at(classify); + + + /* if (c == 0) { double pb = cd->nextSegmentation.percussiveBelow; @@ -682,6 +710,7 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) m_guide.updateGuidance(instantaneousRatio, classifyScale->mag.data(), classifyScale->prevMag.data(), + m_mixedClassifyMags.data(), cd->segmentation, cd->prevSegmentation, cd->nextSegmentation, diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index ab6db5f..0aa8ffa 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -259,6 +259,7 @@ protected: Guide m_guide; Guide::Configuration m_guideConfiguration; ChannelAssembly m_channelAssembly; + FixedVector m_mixedClassifyMags; std::unique_ptr m_calculator; std::unique_ptr m_resampler; std::atomic m_inhop; @@ -268,7 +269,8 @@ protected: void consume(); void calculateHop(); - void analyseChannel(int channel, int inhop, int prevInhop, int prevOuthop); + void analyseChannel(int channel, int inhop, int prevInhop); + void guideChannel(int channel, int prevInhop, int prevOuthop); void analyseFormant(int channel); void adjustFormant(int channel); void synthesiseChannel(int channel, int outhop); From 41e726b510ca4fa8c379a925954ff5866371f613 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 9 Jun 2022 15:07:47 +0100 Subject: [PATCH 081/184] Remove debug out --- src/finer/Guide.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/finer/Guide.h b/src/finer/Guide.h index a779da0..fe9c805 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -256,7 +256,7 @@ public: guidance.fftBands[0].f0 = 0.0; guidance.fftBands[0].f1 = lower; - std::cout << "x:" << lower << std::endl; +// std::cout << "x:" << lower << std::endl; guidance.fftBands[1].f0 = lower; guidance.fftBands[1].f1 = higher; From 4a9574b7eed7eca886c904626bbafd28c95c31d9 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 9 Jun 2022 15:26:16 +0100 Subject: [PATCH 082/184] Backed out changeset b1275ea1b6c8 This makes very little perceptible difference, possibly for the worse, and it is more complicated --- src/finer/Guide.h | 5 ++--- src/finer/R3StretcherImpl.cpp | 33 ++------------------------------- src/finer/R3StretcherImpl.h | 4 +--- 3 files changed, 5 insertions(+), 37 deletions(-) diff --git a/src/finer/Guide.h b/src/finer/Guide.h index fe9c805..6f46381 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -140,7 +140,6 @@ public: void updateGuidance(double ratio, const double *const magnitudes, const double *const prevMagnitudes, - const double *const channelMixedMagnitudes, const BinSegmenter::Segmentation &segmentation, const BinSegmenter::Segmentation &prevSegmentation, const BinSegmenter::Segmentation &nextSegmentation, @@ -222,13 +221,13 @@ public: if (lower > m_maxLower) lower = m_maxLower; */ double prevLower = guidance.fftBands[0].f1; - double lower = descendToValley(prevLower, channelMixedMagnitudes); + 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, channelMixedMagnitudes); + double higher = descendToValley(prevHigher, magnitudes); if (higher > m_maxHigher || higher < m_minHigher) { higher = m_defaultHigher; } diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 22580ce..f10483c 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -38,7 +38,6 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, m_guide(Guide::Parameters(m_parameters.sampleRate, parameters.logger)), m_guideConfiguration(m_guide.getConfiguration()), m_channelAssembly(m_parameters.channels), - m_mixedClassifyMags(m_guideConfiguration.classificationFftSize / 2 + 1, 0.0), m_inhop(1), m_prevOuthop(1), m_draining(false) @@ -390,24 +389,7 @@ R3StretcherImpl::consume() // Analysis for (int c = 0; c < channels; ++c) { - analyseChannel(c, inhop, m_prevInhop); - } - - for (int c = 0; c < channels; ++c) { - auto &classifyScale = - m_channelData.at(c)-> - scales.at(m_guideConfiguration.classificationFftSize); - if (c == 0) { - v_copy(m_mixedClassifyMags.data(), classifyScale->mag.data(), - classifyScale->mag.size()); - } else { - v_add(m_mixedClassifyMags.data(), classifyScale->mag.data(), - classifyScale->mag.size()); - } - } - - for (int c = 0; c < channels; ++c) { - guideChannel(c, m_prevInhop, m_prevOuthop); + analyseChannel(c, inhop, m_prevInhop, m_prevOuthop); } // Phase update. This is synchronised across all channels @@ -483,7 +465,7 @@ R3StretcherImpl::consume() } void -R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop) +R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) { int longest = m_guideConfiguration.longestFftSize; int classify = m_guideConfiguration.classificationFftSize; @@ -669,17 +651,7 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop) cd->prevSegmentation = cd->segmentation; cd->segmentation = cd->nextSegmentation; cd->nextSegmentation = cd->segmenter->segment(cd->nextClassification.data()); -} -void -R3StretcherImpl::guideChannel(int c, int prevInhop, int prevOuthop) -{ - auto &cd = m_channelData.at(c); - int classify = m_guideConfiguration.classificationFftSize; - auto &classifyScale = cd->scales.at(classify); - - - /* if (c == 0) { double pb = cd->nextSegmentation.percussiveBelow; @@ -710,7 +682,6 @@ R3StretcherImpl::guideChannel(int c, int prevInhop, int prevOuthop) m_guide.updateGuidance(instantaneousRatio, classifyScale->mag.data(), classifyScale->prevMag.data(), - m_mixedClassifyMags.data(), cd->segmentation, cd->prevSegmentation, cd->nextSegmentation, diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 0aa8ffa..ab6db5f 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -259,7 +259,6 @@ protected: Guide m_guide; Guide::Configuration m_guideConfiguration; ChannelAssembly m_channelAssembly; - FixedVector m_mixedClassifyMags; std::unique_ptr m_calculator; std::unique_ptr m_resampler; std::atomic m_inhop; @@ -269,8 +268,7 @@ protected: void consume(); void calculateHop(); - void analyseChannel(int channel, int inhop, int prevInhop); - void guideChannel(int channel, int prevInhop, int prevOuthop); + void analyseChannel(int channel, int inhop, int prevInhop, int prevOuthop); void analyseFormant(int channel); void adjustFormant(int channel); void synthesiseChannel(int channel, int outhop); From a7353d71614a9705c7d6da3455f74f212394811b Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 9 Jun 2022 15:39:06 +0100 Subject: [PATCH 083/184] Tidy --- src/finer/Guide.h | 40 ----------------------------------- src/finer/R3StretcherImpl.cpp | 2 -- 2 files changed, 42 deletions(-) diff --git a/src/finer/Guide.h b/src/finer/Guide.h index 6f46381..0212dd1 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -213,13 +213,7 @@ public: guidance.phaseReset.f1 = std::max(segmentation.residualAbove, nextSegmentation.residualAbove); } -/* - double higher = snapToTrough(m_defaultHigher, troughs); - if (higher > m_maxHigher) higher = m_maxHigher; - double lower = snapToTrough(m_defaultLower, troughs); - if (lower > m_maxLower) lower = m_maxLower; -*/ double prevLower = guidance.fftBands[0].f1; double lower = descendToValley(prevLower, magnitudes); if (lower > m_maxLower || lower < m_minLower) { @@ -232,26 +226,6 @@ public: higher = m_defaultHigher; } - -/* - double higher = snapToTrough(prevHigher, troughs, magnitudes); - if (higher < m_minHigher || higher > m_maxHigher) { - higher = snapToTrough(m_defaultHigher, troughs, magnitudes); - if (higher < m_minHigher || higher > m_maxHigher) { - higher = prevHigher; - } - } -*/ - /* - double higher = m_defaultHigher; - - if (lower < m_minLower || lower > m_maxLower) { - lower = snapToTrough(m_defaultLower, troughs, magnitudes); - if (lower < m_minLower || lower > m_maxLower) { - lower = prevLower; - } - } - */ guidance.fftBands[0].f0 = 0.0; guidance.fftBands[0].f1 = lower; @@ -354,9 +328,7 @@ protected: } double descendToValley(double f, const double *const magnitudes) const { -// return frequencyForBin(troughs[binForFrequency(f)]); int b = binForFrequency(f); - for (int i = 0; i < 3; ++i) { if (magnitudes[b+1] < magnitudes[b]) { ++b; @@ -366,18 +338,6 @@ protected: break; } } - -/* - int snapped = troughs[bin]; - std::cout << "snapToTrough: " << f << " -> bin " << bin << " -> snapped " << snapped << " -> " << sf << std::endl; - for (int i = -3; i <= 3; ++i) { - if (i == 0) std::cout << "["; - std::cout << magnitudes[bin + i]; - if (i == 0) std::cout << "]"; - std::cout << " "; - } - std::cout << std::endl; -*/ double sf = frequencyForBin(b); return sf; } diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index f10483c..836ebf4 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -652,7 +652,6 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) 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; @@ -674,7 +673,6 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) } std::cout << std::endl; } -*/ double instantaneousRatio = double(prevOuthop) / double(prevInhop); bool specialCaseUnity = true; From 14394141b06d810a1a8dc214e6e7d72d64af4829 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 9 Jun 2022 16:00:27 +0100 Subject: [PATCH 084/184] Comment out debug --- src/finer/R3StretcherImpl.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 836ebf4..95f088c 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -651,7 +651,7 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) 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; @@ -673,7 +673,7 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) } std::cout << std::endl; } - +*/ double instantaneousRatio = double(prevOuthop) / double(prevInhop); bool specialCaseUnity = true; From bc764c62ea251fc2e12a798e2f76bf0f2bf86d38 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 9 Jun 2022 16:25:11 +0100 Subject: [PATCH 085/184] Restrict range a little more --- src/finer/Guide.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/finer/Guide.h b/src/finer/Guide.h index 0212dd1..0fc312d 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -114,7 +114,7 @@ public: 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(350.0), m_minHigher(2400.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) { From 2298b6786935621e19c2bbbce6deb1d39e02d1a1 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 9 Jun 2022 16:39:30 +0100 Subject: [PATCH 086/184] Avoid recalculating window scale factor every time --- src/finer/R3StretcherImpl.cpp | 16 ++-------------- src/finer/R3StretcherImpl.h | 12 +++++++++++- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 95f088c..327ff1d 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -532,8 +532,6 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) // where the inhop has changed as above, in which case we need to // do both readahead and current) - v_fftshift(readahead.timeDomain.data(), classify); - if (haveValidReadahead) { v_copy(classifyScale->mag.data(), readahead.mag.data(), @@ -543,6 +541,7 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) classifyScale->bufSize); } + v_fftshift(readahead.timeDomain.data(), classify); m_scaleData.at(classify)->fft.forward(readahead.timeDomain.data(), classifyScale->real.data(), classifyScale->imag.data()); @@ -776,18 +775,7 @@ R3StretcherImpl::synthesiseChannel(int c, int outhop) auto &scale = cd->scales.at(fftSize); auto &scaleData = m_scaleData.at(fftSize); - //!!! messy and slow, but leave it until we've - //!!! discovered whether we need a window accumulator - //!!! (we probably do) - int analysisWindowSize = scaleData->analysisWindow.getSize(); - int synthesisWindowSize = scaleData->synthesisWindow.getSize(); - int offset = (analysisWindowSize - synthesisWindowSize) / 2; - double winscale = 0.0; - for (int i = 0; i < synthesisWindowSize; ++i) { - winscale += scaleData->analysisWindow.getValue(i + offset) * - scaleData->synthesisWindow.getValue(i); - } - winscale = double(outhop) / winscale; + double winscale = double(outhop) / scaleData->windowScaleFactor; // The frequency filter is applied naively in the frequency // domain. Aliasing is reduced by the shorter resynthesis diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index ab6db5f..7d5c8ee 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -233,6 +233,7 @@ protected: FFT fft; Window analysisWindow; Window synthesisWindow; + double windowScaleFactor; GuidedPhaseAdvance guided; ScaleData(GuidedPhaseAdvance::Parameters guidedParameters) : fftSize(guidedParameters.fftSize), @@ -241,7 +242,16 @@ protected: analysisWindowLength(fftSize)), synthesisWindow(synthesisWindowShape(fftSize), synthesisWindowLength(fftSize)), - guided(guidedParameters) { } + guided(guidedParameters), + windowScaleFactor(0.0) + { + 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); From 78b491fb4ca2342ecd505d7c278c0fb14d605ff4 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 9 Jun 2022 17:23:26 +0100 Subject: [PATCH 087/184] Perform phase resets when squashing as well as stretching --- src/finer/Guide.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/finer/Guide.h b/src/finer/Guide.h index 0fc312d..0614231 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -199,19 +199,18 @@ public: } double bigGap = 4000.0; - if (ratio > 1.0 && - segmentation.residualAbove > + 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; } - guidance.phaseReset.f1 = std::max(segmentation.residualAbove, - nextSegmentation.residualAbove); } double prevLower = guidance.fftBands[0].f1; From 10e2c13551d90fb0fa6867bb8a9e4821d7ede64b Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 9 Jun 2022 17:25:23 +0100 Subject: [PATCH 088/184] Tidy --- src/finer/PhaseAdvance.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/finer/PhaseAdvance.h b/src/finer/PhaseAdvance.h index 3789b79..79c7db0 100644 --- a/src/finer/PhaseAdvance.h +++ b/src/finer/PhaseAdvance.h @@ -223,10 +223,6 @@ public: m_prevOutPhase[c][i] = outPhase[c][i]; } } - -// int **tmp = m_prevPeaks; -// m_prevPeaks = m_currentPeaks; -// m_currentPeaks = tmp; } protected: From 6940ad29d892bd8dfb40f158a01b66d2bf8f7b6b Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 10 Jun 2022 12:35:15 +0100 Subject: [PATCH 089/184] Introduce a histogram filter for filtering discrete values --- src/common/HistogramFilter.h | 176 ++++++++++++++++++++++++++++ src/common/MovingMedian.h | 5 + src/common/RingBuffer.h | 1 - src/common/SingleThreadRingBuffer.h | 144 +++++++++++++++++++++++ src/finer/BinSegmenter.h | 7 +- src/test/TestSignalBits.cpp | 109 +++++++++++++++++ 6 files changed, 438 insertions(+), 4 deletions(-) create mode 100644 src/common/HistogramFilter.h create mode 100644 src/common/SingleThreadRingBuffer.h diff --git a/src/common/HistogramFilter.h b/src/common/HistogramFilter.h new file mode 100644 index 0000000..50aa137 --- /dev/null +++ b/src/common/HistogramFilter.h @@ -0,0 +1,176 @@ +/* -*- 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) { + } + ~HistogramFilter() { } + + int getFilterLength() const { + return m_buffer.getSize(); + } + + void reset() { + m_buffer.reset(); + for (int i = 0; i < m_histogram.size(); ++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); + ++m_histogram[value]; + } + + void drop() { + if (m_buffer.getReadSpace() > 0) { + int toDrop = m_buffer.readOne(); + --m_histogram[toDrop]; + } + } + + /** 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; + for (int i = 0; i < m_histogram.size(); ++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 { + int max = 0; + int mode = 0; + for (int i = 0; i < m_histogram.size(); ++i) { + int h = m_histogram[i]; + if (i == 0 || h > max) { + max = h; + mode = i; + } + } + 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; + int m_mode; +}; + +} + +#endif diff --git a/src/common/MovingMedian.h b/src/common/MovingMedian.h index e8d40ed..1dfc3ed 100644 --- a/src/common/MovingMedian.h +++ b/src/common/MovingMedian.h @@ -107,6 +107,11 @@ private: // precondition: sorted contains m_length values, one of which is toDrop // postcondition: sorted contains m_length 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_length; T *sorted = sortedFor(filter); int dropIx; diff --git a/src/common/RingBuffer.h b/src/common/RingBuffer.h index a905a33..2fabd14 100644 --- a/src/common/RingBuffer.h +++ b/src/common/RingBuffer.h @@ -44,7 +44,6 @@ namespace RubberBand { * RingBuffer is thread-safe provided only one thread writes and only * one thread reads. */ - template class RingBuffer { diff --git a/src/common/SingleThreadRingBuffer.h b/src/common/SingleThreadRingBuffer.h new file mode 100644 index 0000000..e3e4925 --- /dev/null +++ b/src/common/SingleThreadRingBuffer.h @@ -0,0 +1,144 @@ +/* -*- 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) { } + + 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; + +private: + SingleThreadRingBuffer(const SingleThreadRingBuffer &); // not provided + SingleThreadRingBuffer &operator=(const SingleThreadRingBuffer &); // not provided +}; + +} + +#endif + diff --git a/src/finer/BinSegmenter.h b/src/finer/BinSegmenter.h index dc4dee0..5b2c7d3 100644 --- a/src/finer/BinSegmenter.h +++ b/src/finer/BinSegmenter.h @@ -25,6 +25,7 @@ #define RUBBERBAND_BIN_SEGMENTER_H #include "BinClassifier.h" +#include "../common/HistogramFilter.h" #include @@ -54,7 +55,7 @@ public: BinSegmenter(Parameters parameters) : m_parameters(parameters), m_numeric(m_parameters.binCount, 0), - m_classFilter(15) + m_classFilter(3, 15) { } @@ -70,7 +71,7 @@ public: m_numeric[i] = 2; break; } } - MovingMedian::filter(m_classFilter, m_numeric); + HistogramFilter::modalFilter(m_classFilter, m_numeric); /* std::cout << "c:"; for (int i = 0; i < n; ++i) { @@ -125,7 +126,7 @@ public: protected: Parameters m_parameters; std::vector m_numeric; - MovingMedian m_classFilter; + HistogramFilter m_classFilter; //!!! dupes int binForFrequency(double f) const { diff --git a/src/test/TestSignalBits.cpp b/src/test/TestSignalBits.cpp index 73dc467..383fbcd 100644 --- a/src/test/TestSignalBits.cpp +++ b/src/test/TestSignalBits.cpp @@ -25,6 +25,7 @@ #include #include "../common/MovingMedian.h" +#include "../common/HistogramFilter.h" #include "../finer/Peak.h" using namespace RubberBand; @@ -94,6 +95,114 @@ BOOST_AUTO_TEST_CASE(moving_median_n_1) 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); From 6723ca3636cb018ebcf40326843d835de26453a8 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 10 Jun 2022 12:57:08 +0100 Subject: [PATCH 090/184] Use SingleThreadRingBuffer for MovingMedian --- src/common/MovingMedian.h | 86 ++++++++++++++++++++++------- src/common/SingleThreadRingBuffer.h | 13 +++-- 2 files changed, 76 insertions(+), 23 deletions(-) diff --git a/src/common/MovingMedian.h b/src/common/MovingMedian.h index 1dfc3ed..bdd743c 100644 --- a/src/common/MovingMedian.h +++ b/src/common/MovingMedian.h @@ -27,6 +27,7 @@ #include "SampleFilter.h" #include "FixedVector.h" #include "Allocators.h" +#include "SingleThreadRingBuffer.h" #include #include @@ -41,7 +42,8 @@ class MovingMedianStack { public: MovingMedianStack(int nfilters, int filterLength, float percentile = 50.f) : - m_buffer(nfilters * filterLength * 2, {}), + m_buffers(nfilters, filterLength), + m_sortspace(nfilters * filterLength, {}), m_length(filterLength) { setPercentile(percentile); @@ -51,7 +53,7 @@ public: } int getNFilters() const { - return m_buffer.size() / (m_length * 2); + return m_buffers.size(); } int getSize() const { @@ -69,11 +71,15 @@ public: std::cerr << "WARNING: MovingMedian: NaN encountered" << std::endl; value = T(); } - T *frame = frameFor(filter); - T toDrop = frame[0]; - v_move(frame, frame+1, m_length-1); - frame[m_length-1] = value; - dropAndPut(filter, toDrop, value); + auto &buf = m_buffers[filter]; + if (buf.getWriteSpace() == 0) { + T toDrop = buf.readOne(); + dropAndPut(filter, toDrop, value); + buf.writeOne(value); + } else { + put(filter, value); + buf.writeOne(value); + } } T get(int filter) const { @@ -82,25 +88,21 @@ public: } void reset() { - v_zero(m_buffer.data(), m_buffer.size()); + for (auto &buf : m_buffers) buf.reset(); + v_zero(m_sortspace.data(), m_sortspace.size()); } private: - FixedVector m_buffer; + FixedVector> m_buffers; + FixedVector m_sortspace; int m_length; int m_index; - const T *frameFor(int filter) const { - return m_buffer.data() + filter * m_length * 2; - } - T *frameFor(int filter) { - return m_buffer.data() + filter * m_length * 2; - } const T *sortedFor(int filter) const { - return frameFor(filter) + m_length; + return m_sortspace.data() + filter * m_length; } T *sortedFor(int filter) { - return frameFor(filter) + m_length; + return m_sortspace.data() + filter * m_length; } void dropAndPut(int filter, const T &toDrop, const T &toPut) { @@ -134,7 +136,7 @@ private: std::cout << "toDrop = " << toDrop << ", dropIx = " << dropIx << std::endl; std::cout << "toPut = " << toPut << std::endl; if (sorted[dropIx] != toDrop) { - throw std::runtime_error("element not found"); + throw std::logic_error("element not found"); } #endif @@ -168,7 +170,53 @@ private: std::cout << "]" << std::endl; if (!std::is_sorted(sorted, sorted + n)) { - throw std::runtime_error("array is not sorted"); + throw std::logic_error("array is not sorted"); + } +#endif + } + + void put(int filter, const T &toPut) { + // precondition: sorted contains fewer than m_length values, + // packed at the start + // postcondition: sorted contains up to m_length values, + // packed at the start, one of which is toPut + const int n = m_buffers[filter].getReadSpace(); // items in sorted + +#ifdef DEBUG_MM + if (n >= m_length) { + throw std::logic_error("length mismatch"); + } +#endif + + T *sorted = sortedFor(filter); + int putIx = std::lower_bound(sorted, sorted + n, toPut) - sorted; + +#ifdef DEBUG_MM + std::cout << "\nbefore: ["; + for (int i = 0; i < n; ++i) { + if (i > 0) std::cout << ","; + std::cout << sorted[i]; + } + std::cout << "]" << std::endl; + + std::cout << "toPut = " << toPut << ", putIx = " << putIx << std::endl; +#endif + + if (putIx < n) { + v_move(sorted + putIx + 1, sorted + putIx, n - putIx); + } + sorted[putIx] = toPut; + +#ifdef DEBUG_MM + std::cout << "after: ["; + for (int i = 0; i < n + 1; ++i) { + if (i > 0) std::cout << ","; + std::cout << sorted[i]; + } + std::cout << "]" << std::endl; + + if (!std::is_sorted(sorted, sorted + n)) { + throw std::logic_error("array is not sorted"); } #endif } diff --git a/src/common/SingleThreadRingBuffer.h b/src/common/SingleThreadRingBuffer.h index e3e4925..2e109ec 100644 --- a/src/common/SingleThreadRingBuffer.h +++ b/src/common/SingleThreadRingBuffer.h @@ -56,6 +56,15 @@ public: 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() { } /** @@ -132,10 +141,6 @@ protected: int m_writer; int m_reader; int m_size; - -private: - SingleThreadRingBuffer(const SingleThreadRingBuffer &); // not provided - SingleThreadRingBuffer &operator=(const SingleThreadRingBuffer &); // not provided }; } From 0bfa94a76a4fe78c027eac271e6700a1035fb541 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 10 Jun 2022 13:09:48 +0100 Subject: [PATCH 091/184] Restore MovingMedian to the simpler single filter and provide ..Stack separately. Seems little point in coalescing memory there, now we have separate ring buffers anyway --- src/common/MovingMedian.h | 169 ++++++++++++++++++-------------------- 1 file changed, 78 insertions(+), 91 deletions(-) diff --git a/src/common/MovingMedian.h b/src/common/MovingMedian.h index bdd743c..ce81380 100644 --- a/src/common/MovingMedian.h +++ b/src/common/MovingMedian.h @@ -38,74 +38,96 @@ namespace RubberBand { template -class MovingMedianStack +class MovingMedian : public SampleFilter { public: - MovingMedianStack(int nfilters, int filterLength, float percentile = 50.f) : - m_buffers(nfilters, filterLength), - m_sortspace(nfilters * filterLength, {}), - m_length(filterLength) + MovingMedian(int filterLength, float percentile = 50.f) : + m_buffer(filterLength), + m_sortspace(filterLength, {}) { setPercentile(percentile); } - ~MovingMedianStack() { + ~MovingMedian() { } - int getNFilters() const { - return m_buffers.size(); - } - + MovingMedian(const MovingMedian &) =default; + MovingMedian &operator=(const MovingMedian &) =default; + int getSize() const { - return m_length; + return m_buffer.getSize(); } void setPercentile(float p) { - m_index = int((m_length * p) / 100.f); - if (m_index >= m_length) m_index = m_length-1; + int length = getSize(); + m_index = int((length * p) / 100.f); + if (m_index >= length) m_index = length-1; if (m_index < 0) m_index = 0; } - void push(int filter, T value) { + void push(T value) { if (value != value) { std::cerr << "WARNING: MovingMedian: NaN encountered" << std::endl; value = T(); } - auto &buf = m_buffers[filter]; - if (buf.getWriteSpace() == 0) { - T toDrop = buf.readOne(); - dropAndPut(filter, toDrop, value); - buf.writeOne(value); + if (m_buffer.getWriteSpace() == 0) { + T toDrop = m_buffer.readOne(); + dropAndPut(toDrop, value); + m_buffer.writeOne(value); } else { - put(filter, value); - buf.writeOne(value); + put(value); + m_buffer.writeOne(value); } } - T get(int filter) const { - const T *sorted = sortedFor(filter); - return sorted[m_index]; + T get() const { + return m_sortspace[m_index]; } void reset() { - for (auto &buf : m_buffers) buf.reset(); + m_buffer.reset(); v_zero(m_sortspace.data(), m_sortspace.size()); } + + // 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 &mm, T *v, int n) { + int fn = mm.getSize(); + int lag = fn / 2; + mm.reset(); + int i = 0; + for (; i < lag; ++i) { + if (i < n) mm.push(v[i]); + } + for (; i < n; ++i) { + mm.push(v[i]); + v[i-lag] = mm.get(); + } + for (; i < lag; ++i) { + // just for the unusual case where lag > n + mm.push(T()); + (void)mm.get(); + } + for (; i < n + lag; ++i) { + mm.push(T()); + v[i-lag] = mm.get(); + } + } + + // As above but with a vector argument + // + static void filter(MovingMedian &mm, std::vector &v) { + filter(mm, v.data(), v.size()); + } private: - FixedVector> m_buffers; - FixedVector m_sortspace; - int m_length; + SingleThreadRingBuffer m_buffer; + std::vector m_sortspace; int m_index; - const T *sortedFor(int filter) const { - return m_sortspace.data() + filter * m_length; - } - T *sortedFor(int filter) { - return m_sortspace.data() + filter * m_length; - } - - void dropAndPut(int filter, const T &toDrop, const T &toPut) { + void dropAndPut(const T &toDrop, const T &toPut) { // precondition: sorted contains m_length values, one of which is toDrop // postcondition: sorted contains m_length values, one of which is toPut // (and one instance of toDrop has been removed) @@ -114,8 +136,8 @@ private: // longer than maybe 16 items). Two binary searches plus a // memmove should be faster for longer ones. - const int n = m_length; - T *sorted = sortedFor(filter); + const int n = getSize(); + T *sorted = m_sortspace.data(); int dropIx; if (toDrop <= *sorted) { // this is quite a common short-circuit in situations @@ -175,12 +197,12 @@ private: #endif } - void put(int filter, const T &toPut) { + void put(const T &toPut) { // precondition: sorted contains fewer than m_length values, // packed at the start // postcondition: sorted contains up to m_length values, // packed at the start, one of which is toPut - const int n = m_buffers[filter].getReadSpace(); // items in sorted + const int n = m_buffer.getReadSpace(); // items in sorted #ifdef DEBUG_MM if (n >= m_length) { @@ -188,7 +210,7 @@ private: } #endif - T *sorted = sortedFor(filter); + T *sorted = m_sortspace.data(); int putIx = std::lower_bound(sorted, sorted + n, toPut) - sorted; #ifdef DEBUG_MM @@ -220,81 +242,46 @@ private: } #endif } - - MovingMedianStack(const MovingMedianStack &) =delete; - MovingMedianStack &operator=(const MovingMedianStack &) =delete; }; template -class MovingMedian : public SampleFilter +class MovingMedianStack { public: - MovingMedian(int size, float percentile = 50.f) : - m_mm(1, size, percentile) + MovingMedianStack(int nfilters, int size, float percentile = 50.f) : + m_stack(nfilters, { size, percentile }) { } - ~MovingMedian() { + ~MovingMedianStack() { } int getSize() const { - return m_mm.getSize(); + return m_stack[0].getSize(); } void setPercentile(float p) { - m_mm.setPercentile(p); + for (auto &f: m_stack) { + f.setPercentile(p); + } } - void push(T value) { - m_mm.push(0, value); + void push(int filter, T value) { + m_stack[filter].push(value); } - T get() const { - return m_mm.get(0); + T get(int filter) const { + return m_stack[filter].get(); } void reset() { - m_mm.reset(); - } - - // 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 &mm, T *v, int n) { - int fn = mm.getSize(); - int lag = fn / 2; - mm.reset(); - int i = 0; - for (; i < lag; ++i) { - if (i < n) mm.push(v[i]); + for (auto &f: m_stack) { + f.reset(); } - for (; i < n; ++i) { - mm.push(v[i]); - v[i-lag] = mm.get(); - } - for (; i < lag; ++i) { - // just for the unusual case where lag > n - mm.push(T()); - (void)mm.get(); - } - for (; i < n + lag; ++i) { - mm.push(T()); - v[i-lag] = mm.get(); - } - } - - // As above but with a vector argument - // - static void filter(MovingMedian &mm, std::vector &v) { - filter(mm, v.data(), v.size()); } private: - MovingMedianStack m_mm; - - MovingMedian(const MovingMedian &) =delete; - MovingMedian &operator=(const MovingMedian &) =delete; + std::vector> m_stack; }; } From 5dcc499cf9c1bc03886b355c987314bf8f567b1e Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 10 Jun 2022 16:39:32 +0100 Subject: [PATCH 092/184] Now update MovingMedian behaviour to match bsq code (i.e. make it "more" correct) --- src/common/HistogramFilter.h | 20 +++- src/common/MovingMedian.h | 188 ++++++++++++++++++++++++----------- src/test/TestSignalBits.cpp | 13 +-- 3 files changed, 150 insertions(+), 71 deletions(-) diff --git a/src/common/HistogramFilter.h b/src/common/HistogramFilter.h index 50aa137..ca4bd64 100644 --- a/src/common/HistogramFilter.h +++ b/src/common/HistogramFilter.h @@ -44,8 +44,8 @@ class HistogramFilter public: HistogramFilter(int nValues, int filterLength) : m_buffer(filterLength), - m_histogram(nValues, 0) { - } + m_histogram(nValues, 0), + m_mode(-1) { } ~HistogramFilter() { } int getFilterLength() const { @@ -65,13 +65,21 @@ public: --m_histogram[toDrop]; } m_buffer.writeOne(value); - ++m_histogram[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; + } } } @@ -95,6 +103,9 @@ public: * 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; for (int i = 0; i < m_histogram.size(); ++i) { @@ -104,6 +115,7 @@ public: mode = i; } } + m_mode = mode; return mode; } @@ -168,7 +180,7 @@ public: private: SingleThreadRingBuffer m_buffer; std::vector m_histogram; - int m_mode; + mutable int m_mode; }; } diff --git a/src/common/MovingMedian.h b/src/common/MovingMedian.h index ce81380..64edbbc 100644 --- a/src/common/MovingMedian.h +++ b/src/common/MovingMedian.h @@ -37,19 +37,27 @@ 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 = 50.f) : + MovingMedian(int filterLength, float percentile = fifty) : m_buffer(filterLength), - m_sortspace(filterLength, {}) - { - setPercentile(percentile); - } + m_sortspace(filterLength, {}), + m_fill(0), + m_percentile(percentile) + { } - ~MovingMedian() { - } + ~MovingMedian() { } MovingMedian(const MovingMedian &) =default; MovingMedian &operator=(const MovingMedian &) =default; @@ -57,31 +65,44 @@ public: int getSize() const { return m_buffer.getSize(); } - - void setPercentile(float p) { - int length = getSize(); - m_index = int((length * p) / 100.f); - if (m_index >= length) m_index = length-1; - if (m_index < 0) m_index = 0; - } + 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_buffer.getWriteSpace() == 0) { + if (m_fill == getSize()) { T toDrop = m_buffer.readOne(); dropAndPut(toDrop, value); - m_buffer.writeOne(value); } else { put(value); - m_buffer.writeOne(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 { - return m_sortspace[m_index]; + int n = m_fill - 1; + if (m_percentile == fifty) { // exact default value + return m_sortspace[n / 2]; + } else { + int index = float(n) * m_percentile / 100.f; + if (index >= m_fill) index = m_fill - 1; + return m_sortspace[index]; + } } void reset() { @@ -93,26 +114,22 @@ public: // in-place. Array has length n. Modifies both the filter and the // array. // - static void filter(MovingMedian &mm, T *v, int n) { - int fn = mm.getSize(); - int lag = fn / 2; - mm.reset(); - int i = 0; - for (; i < lag; ++i) { - if (i < n) mm.push(v[i]); - } - for (; i < n; ++i) { - mm.push(v[i]); - v[i-lag] = mm.get(); - } - for (; i < lag; ++i) { - // just for the unusual case where lag > n - mm.push(T()); - (void)mm.get(); - } - for (; i < n + lag; ++i) { - mm.push(T()); - v[i-lag] = mm.get(); + 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; } } @@ -125,18 +142,26 @@ public: private: SingleThreadRingBuffer m_buffer; std::vector m_sortspace; - int m_index; + int m_fill; + float m_percentile; void dropAndPut(const T &toDrop, const T &toPut) { - // precondition: sorted contains m_length values, one of which is toDrop - // postcondition: sorted contains m_length values, one of which is 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 = getSize(); + const int n = m_fill; + +#ifdef DEBUG_MM + if (n != getSize()) { + throw std::logic_error("length mismatch"); + } +#endif + T *sorted = m_sortspace.data(); int dropIx; if (toDrop <= *sorted) { @@ -158,7 +183,7 @@ private: std::cout << "toDrop = " << toDrop << ", dropIx = " << dropIx << std::endl; std::cout << "toPut = " << toPut << std::endl; if (sorted[dropIx] != toDrop) { - throw std::logic_error("element not found"); + throw std::logic_error("element not found (a)"); } #endif @@ -198,14 +223,15 @@ private: } void put(const T &toPut) { - // precondition: sorted contains fewer than m_length values, + // precondition: sorted contains m_fill values, m_fill < m_length, // packed at the start - // postcondition: sorted contains up to m_length values, + // postcondition: m_fill is incremented, sorted contains m_fill values, // packed at the start, one of which is toPut - const int n = m_buffer.getReadSpace(); // items in sorted + + int n = m_fill; #ifdef DEBUG_MM - if (n >= m_length) { + if (n >= getSize()) { throw std::logic_error("length mismatch"); } #endif @@ -229,27 +255,81 @@ private: } sorted[putIx] = toPut; + ++m_fill; + #ifdef DEBUG_MM std::cout << "after: ["; - for (int i = 0; i < n + 1; ++i) { + for (int i = 0; i < m_fill; ++i) { if (i > 0) std::cout << ","; std::cout << sorted[i]; } std::cout << "]" << std::endl; - if (!std::is_sorted(sorted, sorted + n)) { + if (!std::is_sorted(sorted, sorted + m_fill)) { throw std::logic_error("array is not sorted"); } #endif } + + 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; + +#ifdef DEBUG_MM + if (n <= 0 || n > getSize()) { + throw std::logic_error("length mismatch"); + } +#endif + + T *sorted = m_sortspace.data(); + int dropIx = std::lower_bound(sorted, sorted + n, toDrop) - sorted; + +#ifdef DEBUG_MM + std::cout << "\nbefore: ["; + for (int i = 0; i < n; ++i) { + if (i > 0) std::cout << ","; + std::cout << sorted[i]; + } + std::cout << "]" << std::endl; + + std::cout << "toDrop = " << toDrop << ", dropIx = " << dropIx << std::endl; + if (sorted[dropIx] != toDrop) { + throw std::logic_error("element not found (b)"); + } +#endif + + if (dropIx < n - 1) { + v_move(sorted + dropIx, sorted + dropIx + 1, n - dropIx - 1); + } + + --m_fill; + +#ifdef DEBUG_MM + std::cout << "after: ["; + for (int i = 0; i < m_fill; ++i) { + if (i > 0) std::cout << ","; + std::cout << sorted[i]; + } + std::cout << "]" << std::endl; + + if (!std::is_sorted(sorted, sorted + m_fill)) { + throw std::logic_error("array is not sorted"); + } +#endif + } + }; template class MovingMedianStack { public: - MovingMedianStack(int nfilters, int size, float percentile = 50.f) : - m_stack(nfilters, { size, percentile }) + MovingMedianStack(int nfilters, int size) : + m_stack(nfilters, { size }) { } @@ -260,12 +340,6 @@ public: return m_stack[0].getSize(); } - void setPercentile(float p) { - for (auto &f: m_stack) { - f.setPercentile(p); - } - } - void push(int filter, T value) { m_stack[filter].push(value); } diff --git a/src/test/TestSignalBits.cpp b/src/test/TestSignalBits.cpp index 383fbcd..8c6e106 100644 --- a/src/test/TestSignalBits.cpp +++ b/src/test/TestSignalBits.cpp @@ -34,13 +34,6 @@ namespace tt = boost::test_tools; BOOST_AUTO_TEST_SUITE(TestSignalBits) -// NB our moving median has different lag behaviour from bsq - we -// begin padded with zeros, while bsq begins with an empty vector. The -// bsq behaviour is imho more correct, and this really shows up in the -// n_1 case below (where the correct answer is surely {1.0} rather -// than {0.0}) but ours is not wholly wrong, more efficient, "usually -// fine" - BOOST_AUTO_TEST_CASE(moving_median_simple_3) { MovingMedian mm(3); @@ -54,7 +47,7 @@ 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, 3.0, 3.0, 3.0 }; + vector expected { 2.0, 2.0, 3.0, 3.0 }; MovingMedian::filter(mm, arr); BOOST_TEST(arr == expected, tt::per_element()); } @@ -72,7 +65,7 @@ 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 { 1.0e-6, 0.6, 0.6, 1.0e-6 }; + vector expected { 0.6, 0.6, 0.6, 0.6 }; MovingMedian::filter(mm, arr); BOOST_TEST(arr == expected, tt::per_element()); } @@ -90,7 +83,7 @@ BOOST_AUTO_TEST_CASE(moving_median_n_1) { MovingMedian mm(6); vector arr { 1.0 }; - vector expected { 0.0 }; + vector expected { 1.0 }; MovingMedian::filter(mm, arr); BOOST_TEST(arr == expected, tt::per_element()); } From 63bcfb0e22a325b1dd2caf75ecf6a7deca37d684 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 10 Jun 2022 16:44:04 +0100 Subject: [PATCH 093/184] Tidy up --- src/common/MovingMedian.h | 109 -------------------------------------- 1 file changed, 109 deletions(-) diff --git a/src/common/MovingMedian.h b/src/common/MovingMedian.h index 64edbbc..9d16bfc 100644 --- a/src/common/MovingMedian.h +++ b/src/common/MovingMedian.h @@ -32,8 +32,6 @@ #include #include -//#define DEBUG_MM 1 - namespace RubberBand { @@ -155,13 +153,6 @@ private: // memmove should be faster for longer ones. const int n = m_fill; - -#ifdef DEBUG_MM - if (n != getSize()) { - throw std::logic_error("length mismatch"); - } -#endif - T *sorted = m_sortspace.data(); int dropIx; if (toDrop <= *sorted) { @@ -171,22 +162,6 @@ private: } else { dropIx = std::lower_bound(sorted, sorted + n, toDrop) - sorted; } - -#ifdef DEBUG_MM - std::cout << "\nbefore: ["; - for (int i = 0; i < n; ++i) { - if (i > 0) std::cout << ","; - std::cout << sorted[i]; - } - std::cout << "]" << std::endl; - - std::cout << "toDrop = " << toDrop << ", dropIx = " << dropIx << std::endl; - std::cout << "toPut = " << toPut << std::endl; - if (sorted[dropIx] != toDrop) { - throw std::logic_error("element not found (a)"); - } -#endif - if (toPut > toDrop) { int i = dropIx; while (i+1 < n) { @@ -207,19 +182,6 @@ private: } sorted[i+1] = toPut; } - -#ifdef DEBUG_MM - std::cout << "after: ["; - for (int i = 0; i < n; ++i) { - if (i > 0) std::cout << ","; - std::cout << sorted[i]; - } - std::cout << "]" << std::endl; - - if (!std::is_sorted(sorted, sorted + n)) { - throw std::logic_error("array is not sorted"); - } -#endif } void put(const T &toPut) { @@ -227,48 +189,14 @@ private: // 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; - -#ifdef DEBUG_MM - if (n >= getSize()) { - throw std::logic_error("length mismatch"); - } -#endif - T *sorted = m_sortspace.data(); int putIx = std::lower_bound(sorted, sorted + n, toPut) - sorted; - -#ifdef DEBUG_MM - std::cout << "\nbefore: ["; - for (int i = 0; i < n; ++i) { - if (i > 0) std::cout << ","; - std::cout << sorted[i]; - } - std::cout << "]" << std::endl; - - std::cout << "toPut = " << toPut << ", putIx = " << putIx << std::endl; -#endif - if (putIx < n) { v_move(sorted + putIx + 1, sorted + putIx, n - putIx); } sorted[putIx] = toPut; - ++m_fill; - -#ifdef DEBUG_MM - std::cout << "after: ["; - for (int i = 0; i < m_fill; ++i) { - if (i > 0) std::cout << ","; - std::cout << sorted[i]; - } - std::cout << "]" << std::endl; - - if (!std::is_sorted(sorted, sorted + m_fill)) { - throw std::logic_error("array is not sorted"); - } -#endif } void drop(const T &toDrop) { @@ -276,50 +204,13 @@ private: // 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; - -#ifdef DEBUG_MM - if (n <= 0 || n > getSize()) { - throw std::logic_error("length mismatch"); - } -#endif - T *sorted = m_sortspace.data(); int dropIx = std::lower_bound(sorted, sorted + n, toDrop) - sorted; - -#ifdef DEBUG_MM - std::cout << "\nbefore: ["; - for (int i = 0; i < n; ++i) { - if (i > 0) std::cout << ","; - std::cout << sorted[i]; - } - std::cout << "]" << std::endl; - - std::cout << "toDrop = " << toDrop << ", dropIx = " << dropIx << std::endl; - if (sorted[dropIx] != toDrop) { - throw std::logic_error("element not found (b)"); - } -#endif - if (dropIx < n - 1) { v_move(sorted + dropIx, sorted + dropIx + 1, n - dropIx - 1); } - --m_fill; - -#ifdef DEBUG_MM - std::cout << "after: ["; - for (int i = 0; i < m_fill; ++i) { - if (i > 0) std::cout << ","; - std::cout << sorted[i]; - } - std::cout << "]" << std::endl; - - if (!std::is_sorted(sorted, sorted + m_fill)) { - throw std::logic_error("array is not sorted"); - } -#endif } }; From c50397128aaa6cbe4525f5edb726ce5c8fa477b9 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 10 Jun 2022 17:07:23 +0100 Subject: [PATCH 094/184] Fix failure to reset m_fill on reset --- src/common/MovingMedian.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/MovingMedian.h b/src/common/MovingMedian.h index 9d16bfc..48fcd39 100644 --- a/src/common/MovingMedian.h +++ b/src/common/MovingMedian.h @@ -106,6 +106,7 @@ public: 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 From a66b01446f8e18776b4062a772d1658427f24476 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 10 Jun 2022 18:15:58 +0100 Subject: [PATCH 095/184] Pre-pad start of input; now need to un-pad start of output --- src/finer/R3StretcherImpl.cpp | 46 ++++++++++++++++++++++++++++++----- src/finer/R3StretcherImpl.h | 1 + 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 327ff1d..4a3f840 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -40,6 +40,7 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, m_channelAssembly(m_parameters.channels), m_inhop(1), m_prevOuthop(1), + m_totalOutCount(0), m_draining(false) { double maxClassifierFrequency = 16000.0; @@ -115,12 +116,25 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, if (!m_timeRatio.is_lock_free()) { m_parameters.logger("WARNING: std::atomic is not lock-free"); } + + // Pre-fill to half of the longest frame. The centre of the first + // processing frame should be the first sample of the audio. 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. + + if (!(m_parameters.options & RubberBandStretcher::OptionProcessRealTime)) { + for (int c = 0; c < m_parameters.channels; ++c) { + m_channelData[c]->inbuf->zero + (m_guideConfiguration.longestFftSize / 2); + } + } } WindowType R3StretcherImpl::ScaleData::analysisWindowShape(int fftSize) { - if (fftSize == 4096) return HannWindow; + if (fftSize > 2048) return HannWindow; else return NiemitaloForwardWindow; } @@ -133,14 +147,14 @@ R3StretcherImpl::ScaleData::analysisWindowLength(int fftSize) WindowType R3StretcherImpl::ScaleData::synthesisWindowShape(int fftSize) { - if (fftSize == 4096) return HannWindow; + if (fftSize > 2048) return HannWindow; else return NiemitaloReverseWindow; } int R3StretcherImpl::ScaleData::synthesisWindowLength(int fftSize) { - if (fftSize == 4096) return fftSize/2; + if (fftSize > 2048) return HannWindow; else return fftSize; } @@ -221,7 +235,12 @@ R3StretcherImpl::getPitchScale() const size_t R3StretcherImpl::getLatency() const { - return 0; //!!! + if (!(m_parameters.options & RubberBandStretcher::OptionProcessRealTime)) { + return 0; + } else { + double factor = m_pitchScale * 0.5; + return size_t(ceil(m_guideConfiguration.longestFftSize * factor)); + } } size_t @@ -442,14 +461,29 @@ R3StretcherImpl::consume() // Emit + // 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 output. In RT mode we didn't apply any pre-padding, so + // we don't want to remove any here. + + int startSkip = 0; + if (!(m_parameters.options & RubberBandStretcher::OptionProcessRealTime)) { + double factor = m_pitchScale * 0.5; + startSkip = int(ceil(m_guideConfiguration.longestFftSize * factor)); + } + + //!!! now actually do the skipping + for (int c = 0; c < channels; ++c) { auto &cd = m_channelData.at(c); if (m_resampler) { cd->outbuf->write(cd->resampled.data(), resampledCount); + m_totalOutCount += resampledCount; } else { cd->outbuf->write(cd->mixdown.data(), outhop); + m_totalOutCount += outhop; } - + int readSpace = cd->inbuf->getReadSpace(); if (readSpace < inhop) { // This should happen only when draining @@ -458,7 +492,7 @@ R3StretcherImpl::consume() cd->inbuf->skip(inhop); } } - + m_prevInhop = inhop; m_prevOuthop = outhop; } diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 7d5c8ee..0e77108 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -274,6 +274,7 @@ protected: std::atomic m_inhop; int m_prevInhop; int m_prevOuthop; + int64_t m_totalOutCount; bool m_draining; void consume(); From 9f913385dec214438743903b5b4344e306dde1bd Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 10 Jun 2022 20:26:37 +0100 Subject: [PATCH 096/184] Apply start skip. Still work to be done --- src/finer/R3StretcherImpl.cpp | 41 +++++++++++++++-------------------- src/finer/R3StretcherImpl.h | 2 +- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 4a3f840..e62ba09 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -40,7 +40,7 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, m_channelAssembly(m_parameters.channels), m_inhop(1), m_prevOuthop(1), - m_totalOutCount(0), + m_startSkip(0), m_draining(false) { double maxClassifierFrequency = 16000.0; @@ -117,17 +117,18 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, m_parameters.logger("WARNING: std::atomic is not lock-free"); } - // Pre-fill to half of the longest frame. The centre of the first - // processing frame should be the first sample of the audio. 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. + // Pad to half of 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. if (!(m_parameters.options & RubberBandStretcher::OptionProcessRealTime)) { + int pad = m_guideConfiguration.longestFftSize / 2; for (int c = 0; c < m_parameters.channels; ++c) { - m_channelData[c]->inbuf->zero - (m_guideConfiguration.longestFftSize / 2); + m_channelData[c]->inbuf->zero(pad); } + // By the time we skip this later we will have resampled + m_startSkip = int(round(pad / m_pitchScale)); } } @@ -461,27 +462,12 @@ R3StretcherImpl::consume() // Emit - // 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 output. In RT mode we didn't apply any pre-padding, so - // we don't want to remove any here. - - int startSkip = 0; - if (!(m_parameters.options & RubberBandStretcher::OptionProcessRealTime)) { - double factor = m_pitchScale * 0.5; - startSkip = int(ceil(m_guideConfiguration.longestFftSize * factor)); - } - - //!!! now actually do the skipping - for (int c = 0; c < channels; ++c) { auto &cd = m_channelData.at(c); if (m_resampler) { cd->outbuf->write(cd->resampled.data(), resampledCount); - m_totalOutCount += resampledCount; } else { cd->outbuf->write(cd->mixdown.data(), outhop); - m_totalOutCount += outhop; } int readSpace = cd->inbuf->getReadSpace(); @@ -492,6 +478,15 @@ R3StretcherImpl::consume() cd->inbuf->skip(inhop); } } + + if (m_startSkip > 0) { + int toSkip = std::min + (m_startSkip, m_channelData.at(0)->outbuf->getReadSpace()); + for (int c = 0; c < channels; ++c) { + m_channelData.at(c)->outbuf->skip(toSkip); + } + m_startSkip -= toSkip; + } m_prevInhop = inhop; m_prevOuthop = outhop; diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 0e77108..c022d3c 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -274,7 +274,7 @@ protected: std::atomic m_inhop; int m_prevInhop; int m_prevOuthop; - int64_t m_totalOutCount; + int m_startSkip; bool m_draining; void consume(); From 321a89e372943f5aed0be68dbff6a8bfe2ceb269 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 10 Jun 2022 20:48:17 +0100 Subject: [PATCH 097/184] Fix absurd typo --- src/finer/R3StretcherImpl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index e62ba09..815ade3 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -155,7 +155,7 @@ R3StretcherImpl::ScaleData::synthesisWindowShape(int fftSize) int R3StretcherImpl::ScaleData::synthesisWindowLength(int fftSize) { - if (fftSize > 2048) return HannWindow; + if (fftSize > 2048) return fftSize/2; else return fftSize; } From 05fb61154433e4c8357af801436f6f1980840642 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 13 Jun 2022 09:40:26 +0100 Subject: [PATCH 098/184] Experimental preKick to slightly reduce frame just before kick and boost kick accordingly --- src/finer/BinSegmenter.h | 2 +- src/finer/Guide.h | 40 ++++++++++++++++++++++--------- src/finer/R3StretcherImpl.cpp | 45 ++++++++++++++++++++++++++++++++++- src/finer/R3StretcherImpl.h | 3 +++ 4 files changed, 77 insertions(+), 13 deletions(-) diff --git a/src/finer/BinSegmenter.h b/src/finer/BinSegmenter.h index 5b2c7d3..1e00521 100644 --- a/src/finer/BinSegmenter.h +++ b/src/finer/BinSegmenter.h @@ -55,7 +55,7 @@ public: BinSegmenter(Parameters parameters) : m_parameters(parameters), m_numeric(m_parameters.binCount, 0), - m_classFilter(3, 15) + m_classFilter(3, 18) { } diff --git a/src/finer/Guide.h b/src/finer/Guide.h index 0614231..2204065 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -68,7 +68,7 @@ public: FftBand fftBands[3]; PhaseLockBand phaseLockBands[5]; Range kick; - Range lowPercussive; + Range preKick; Range highPercussive; Range phaseReset; Range channelLock; @@ -140,6 +140,7 @@ public: void updateGuidance(double ratio, const double *const magnitudes, const double *const prevMagnitudes, + const double *const nextMagnitudes, const BinSegmenter::Segmentation &segmentation, const BinSegmenter::Segmentation &prevSegmentation, const BinSegmenter::Segmentation &nextSegmentation, @@ -147,7 +148,7 @@ public: Guidance &guidance) const { guidance.kick.present = false; - guidance.lowPercussive.present = false; + guidance.preKick.present = false; guidance.highPercussive.present = false; guidance.phaseReset.present = false; @@ -180,16 +181,33 @@ public: guidance.channelLock.f0 = 0.0; guidance.channelLock.f1 = 600.0; - if (segmentation.percussiveBelow > 40.0) { - guidance.lowPercussive.present = true; - guidance.lowPercussive.f0 = 0.0; - guidance.lowPercussive.f1 = segmentation.percussiveBelow; - } - - bool potentialKick = checkPotentialKick(magnitudes, prevMagnitudes); + bool kick = + (segmentation.percussiveBelow > 40.0) && + (prevSegmentation.percussiveBelow < 40.0) && + checkPotentialKick(magnitudes, prevMagnitudes); - if (potentialKick && prevSegmentation.percussiveBelow < 40.0) { - guidance.kick = guidance.lowPercussive; + 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) { diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 815ade3..cd73c76 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -436,6 +436,10 @@ R3StretcherImpl::consume() m_prevOuthop); } + for (int c = 0; c < channels; ++c) { + adjustPreKick(c); + } + // Resynthesis for (int c = 0; c < channels; ++c) { @@ -701,18 +705,30 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) } std::cout << std::endl; } -*/ +*/ double instantaneousRatio = double(prevOuthop) / double(prevInhop); bool specialCaseUnity = true; m_guide.updateGuidance(instantaneousRatio, classifyScale->mag.data(), classifyScale->prevMag.data(), + cd->readahead.mag.data(), cd->segmentation, cd->prevSegmentation, cd->nextSegmentation, specialCaseUnity, 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 @@ -781,6 +797,33 @@ R3StretcherImpl::adjustFormant(int c) } } +void +R3StretcherImpl::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); + int to = binForFrequency(cd->guidance.preKick.f1, fftSize); + 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); + int to = binForFrequency(cd->guidance.preKick.f1, fftSize); + for (int i = from; i <= to; ++i) { + scale->mag[i] += scale->pendingKick[i]; + scale->pendingKick[i] = 0.0; + } + } +} + void R3StretcherImpl::synthesiseChannel(int c, int outhop) { diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index c022d3c..ad97207 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -110,6 +110,7 @@ protected: FixedVector phase; FixedVector advancedPhase; FixedVector prevMag; + FixedVector pendingKick; FixedVector accumulator; ChannelScaleData(int _fftSize, int _longestFftSize) : @@ -122,6 +123,7 @@ protected: phase(bufSize, 0.f), advancedPhase(bufSize, 0.f), prevMag(bufSize, 0.f), + pendingKick(bufSize, 0.f), accumulator(_longestFftSize, 0.f) { } @@ -282,6 +284,7 @@ protected: 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); double getEffectiveRatio() const { From 182e2b0e3b5c8690ac498e9a2c103d9503c4c1ba Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 13 Jun 2022 10:08:05 +0100 Subject: [PATCH 099/184] Put binForFrequency/frequencyForBin in a common place --- src/common/mathmisc.h | 56 +++++++++++++++++++++++++++++++++ src/common/sysutils.h | 10 ------ src/faster/StretcherProcess.cpp | 1 + src/finer/BinSegmenter.h | 26 +++++++-------- src/finer/Guide.h | 18 ++++------- src/finer/PhaseAdvance.h | 19 +++++------ src/finer/R3StretcherImpl.cpp | 25 ++++++++------- src/finer/R3StretcherImpl.h | 10 ------ 8 files changed, 95 insertions(+), 70 deletions(-) create mode 100644 src/common/mathmisc.h 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/common/sysutils.h b/src/common/sysutils.h index 798c5d6..06755ec 100644 --- a/src/common/sysutils.h +++ b/src/common/sysutils.h @@ -92,16 +92,6 @@ struct timeval { long tv_sec; long tv_usec; }; void gettimeofday(struct timeval *p, void *tz); #endif // _WIN32 -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 diff --git a/src/faster/StretcherProcess.cpp b/src/faster/StretcherProcess.cpp index c5a8b60..1a4e823 100644 --- a/src/faster/StretcherProcess.cpp +++ b/src/faster/StretcherProcess.cpp @@ -29,6 +29,7 @@ #include "../common/Profiler.h" #include "../common/VectorOps.h" #include "../common/sysutils.h" +#include "../common/mathmisc.h" #include #include diff --git a/src/finer/BinSegmenter.h b/src/finer/BinSegmenter.h index 1e00521..c49f03d 100644 --- a/src/finer/BinSegmenter.h +++ b/src/finer/BinSegmenter.h @@ -25,7 +25,9 @@ #define RUBBERBAND_BIN_SEGMENTER_H #include "BinClassifier.h" + #include "../common/HistogramFilter.h" +#include "../common/mathmisc.h" #include @@ -83,11 +85,12 @@ public: } } std::cout << std::endl; -*/ +*/ double f0 = 0.0; for (int i = 1; i < n; ++i) { if (m_numeric[i] != 1) { - f0 = frequencyForBin(i); + f0 = frequencyForBin + (i, m_parameters.fftSize, m_parameters.sampleRate); break; } } @@ -102,14 +105,17 @@ public: continue; } else if (c == 1) { // percussive inPercussive = true; - f2 = frequencyForBin(i); + f2 = frequencyForBin + (i, m_parameters.fftSize, m_parameters.sampleRate); } else { // harmonic - f1 = f2 = frequencyForBin(i); + f1 = f2 = frequencyForBin + (i, m_parameters.fftSize, m_parameters.sampleRate); break; } } else { // inPercussive if (c != 1) { // non-percussive - f1 = frequencyForBin(i); + f1 = frequencyForBin + (i, m_parameters.fftSize, m_parameters.sampleRate); break; } } @@ -128,16 +134,6 @@ protected: std::vector m_numeric; HistogramFilter m_classFilter; - //!!! dupes - int binForFrequency(double f) const { - return int(round(f * double(m_parameters.fftSize) / - m_parameters.sampleRate)); - } - double frequencyForBin(int b) const { - return (double(b) * m_parameters.sampleRate) - / double(m_parameters.fftSize); - } - BinSegmenter(const BinSegmenter &) =delete; BinSegmenter &operator=(const BinSegmenter &) =delete; }; diff --git a/src/finer/Guide.h b/src/finer/Guide.h index 2204065..905e61d 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -312,15 +312,6 @@ protected: double m_maxLower; double m_maxHigher; - int binForFrequency(double f) const { - return int(round(f * double(m_configuration.classificationFftSize) / - m_parameters.sampleRate)); - } - double frequencyForBin(int b) const { - return (double(b) * m_parameters.sampleRate) - / double(m_configuration.classificationFftSize); - } - // near-dupe with R2 RubberBandStretcher::Impl int roundUp(int value) const { if (value < 1) return 1; @@ -333,7 +324,8 @@ protected: bool checkPotentialKick(const double *const magnitudes, const double *const prevMagnitudes) const { - int b = binForFrequency(200.0); + 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]; @@ -345,7 +337,8 @@ protected: } double descendToValley(double f, const double *const magnitudes) const { - int b = binForFrequency(f); + int b = binForFrequency(f, m_configuration.classificationFftSize, + m_parameters.sampleRate); for (int i = 0; i < 3; ++i) { if (magnitudes[b+1] < magnitudes[b]) { ++b; @@ -355,7 +348,8 @@ protected: break; } } - double sf = frequencyForBin(b); + double sf = frequencyForBin(b, m_configuration.classificationFftSize, + m_parameters.sampleRate); return sf; } diff --git a/src/finer/PhaseAdvance.h b/src/finer/PhaseAdvance.h index 79c7db0..c20cf1d 100644 --- a/src/finer/PhaseAdvance.h +++ b/src/finer/PhaseAdvance.h @@ -26,6 +26,8 @@ #include "Guide.h" +#include "../common/mathmisc.h" + #include #include @@ -129,8 +131,10 @@ public: m_currentPeaks[c][i] = i; } for (const auto &band : guidance[c]->phaseLockBands) { - int startBin = binForFrequency(band.f0); - int endBin = binForFrequency(band.f1); + 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], @@ -176,7 +180,8 @@ public: const Guide::Guidance *g = guidance[c]; int phaseLockBand = 0; for (int i = lowest; i <= highest; ++i) { - double f = frequencyForBin(i); + double f = frequencyForBin + (i, m_parameters.fftSize, m_parameters.sampleRate); while (f > g->phaseLockBands[phaseLockBand].f1) { ++phaseLockBand; } @@ -237,14 +242,6 @@ protected: double **m_unlocked; bool m_reported; - int binForFrequency(double f) const { - return int(round(f * double(m_parameters.fftSize) / - m_parameters.sampleRate)); - } - double frequencyForBin(int b) const { - return (double(b) * m_parameters.sampleRate) - / double(m_parameters.fftSize); - } bool inRange(double f, const Guide::Range &r) { return r.present && f >= r.f0 && f < r.f1; } diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index cd73c76..a0d80d2 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -688,9 +688,9 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) double pb = cd->nextSegmentation.percussiveBelow; double pa = cd->nextSegmentation.percussiveAbove; double ra = cd->nextSegmentation.residualAbove; - int pbb = binForFrequency(pb, classify); - int pab = binForFrequency(pa, classify); - int rab = binForFrequency(ra, classify); + 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; @@ -804,8 +804,10 @@ R3StretcherImpl::adjustPreKick(int 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); - int to = binForFrequency(cd->guidance.preKick.f1, 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) { @@ -815,8 +817,10 @@ R3StretcherImpl::adjustPreKick(int c) } } else if (cd->guidance.kick.present) { auto &scale = cd->scales.at(fftSize); - int from = binForFrequency(cd->guidance.preKick.f0, fftSize); - int to = binForFrequency(cd->guidance.preKick.f1, 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; @@ -852,12 +856,9 @@ R3StretcherImpl::synthesiseChannel(int c, int outhop) // The frequency filter is applied naively in the frequency // domain. Aliasing is reduced by the shorter resynthesis // window - - //!!! I don't think we have binForFrequency etc available in - //!!! this class - really that's ridiculous - int lowBin = int(floor(fftSize * band.f0 / m_parameters.sampleRate)); - int highBin = int(floor(fftSize * band.f1 / m_parameters.sampleRate)); + 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; for (int i = 0; i < lowBin; ++i) { diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index ad97207..14d095d 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -294,16 +294,6 @@ protected: static void logCout(const std::string &message) { std::cout << "RubberBandStretcher: " << message << std::endl; } - - //!!! dupes - int binForFrequency(double f, int fftSize) const { - return int(round(f * double(fftSize) / - m_parameters.sampleRate)); - } - double frequencyForBin(int b, int fftSize) const { - return (double(b) * m_parameters.sampleRate) - / double(fftSize); - } }; } From c7e4d9eb07af50031054315922da4cf2ba1158b1 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 13 Jun 2022 10:39:13 +0100 Subject: [PATCH 100/184] Provide option to shift formant independently of pitch --- rubberband/RubberBandStretcher.h | 38 ++++++++++++++++++++++++++++++++ src/RubberBandStretcher.cpp | 13 +++++++++++ src/finer/R3StretcherImpl.cpp | 17 +++++++++++++- src/finer/R3StretcherImpl.h | 3 +++ 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/rubberband/RubberBandStretcher.h b/rubberband/RubberBandStretcher.h index 14529b9..de4149a 100644 --- a/rubberband/RubberBandStretcher.h +++ b/rubberband/RubberBandStretcher.h @@ -421,6 +421,34 @@ 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). + */ + void setFormantScale(double scale); + /** * Return the last time ratio value that was set (either on * construction or with setTimeRatio()). @@ -433,6 +461,16 @@ 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). + */ + 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 diff --git a/src/RubberBandStretcher.cpp b/src/RubberBandStretcher.cpp index 5e75498..2f8460f 100644 --- a/src/RubberBandStretcher.cpp +++ b/src/RubberBandStretcher.cpp @@ -75,6 +75,12 @@ RubberBandStretcher::setPitchScale(double scale) else m_r3d->setPitchScale(scale); } +void +RubberBandStretcher::setFormantScale(double scale) +{ + if (m_r3d) m_r3d->setFormantScale(scale); +} + double RubberBandStretcher::getTimeRatio() const { @@ -89,6 +95,13 @@ RubberBandStretcher::getPitchScale() const else return m_r3d->getPitchScale(); } +double +RubberBandStretcher::getFormantScale() const +{ + if (m_d) return 0.0; + else return m_r3d->getFormantScale(); +} + size_t RubberBandStretcher::getLatency() const { diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index a0d80d2..c266ca2 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -35,6 +35,7 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, m_parameters(parameters), m_timeRatio(initialTimeRatio), m_pitchScale(initialPitchScale), + m_formantScale(0.0), m_guide(Guide::Parameters(m_parameters.sampleRate, parameters.logger)), m_guideConfiguration(m_guide.getConfiguration()), m_channelAssembly(m_parameters.channels), @@ -173,6 +174,12 @@ R3StretcherImpl::setPitchScale(double scale) calculateHop(); } +void +R3StretcherImpl::setFormantScale(double scale) +{ + m_formantScale = scale; +} + void R3StretcherImpl::setFormantOption(RubberBandStretcher::Options options) { @@ -233,6 +240,12 @@ R3StretcherImpl::getPitchScale() const return m_pitchScale; } +double +R3StretcherImpl::getFormantScale() const +{ + return m_formantScale; +} + size_t R3StretcherImpl::getLatency() const { @@ -777,7 +790,9 @@ R3StretcherImpl::adjustFormant(int c) int highBin = int(floor(fftSize * 10000.0 / m_parameters.sampleRate)); double targetFactor = double(cd->formant->fftSize) / double(fftSize); - double sourceFactor = targetFactor * m_pitchScale; + 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; diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 14d095d..8d2181f 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -69,9 +69,11 @@ public: void setTimeRatio(double ratio); void setPitchScale(double scale); + void setFormantScale(double scale); double getTimeRatio() const; double getPitchScale() const; + double getFormantScale() const; void setFormantOption(RubberBandStretcher::Options); void setPitchOption(RubberBandStretcher::Options); @@ -265,6 +267,7 @@ protected: std::atomic m_timeRatio; std::atomic m_pitchScale; + std::atomic m_formantScale; std::vector> m_channelData; std::map> m_scaleData; From 90ad1274d8c29bc8549ffeb20dca83d0efac352f Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 13 Jun 2022 11:49:04 +0100 Subject: [PATCH 101/184] Add R3 support to plugins --- ladspa-lv2/RubberBandR3PitchShifter.cpp | 646 +++++++++++++++++++ ladspa-lv2/RubberBandR3PitchShifter.h | 149 +++++ ladspa-lv2/ladspa-rubberband.cat | 2 + ladspa-lv2/libmain-ladspa.cpp | 7 +- ladspa-lv2/libmain-lv2.cpp | 7 +- ladspa-lv2/rubberband.lv2/lv2-rubberband.ttl | 110 ++++ ladspa-lv2/rubberband.lv2/manifest.ttl | 10 + src/finer/R3StretcherImpl.cpp | 1 + 8 files changed, 930 insertions(+), 2 deletions(-) create mode 100644 ladspa-lv2/RubberBandR3PitchShifter.cpp create mode 100644 ladspa-lv2/RubberBandR3PitchShifter.h 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/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index c266ca2..a20bf0e 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -190,6 +190,7 @@ R3StretcherImpl::setFormantOption(RubberBandStretcher::Options options) m_parameters.options |= options; } +//!!! unused? void R3StretcherImpl::setPitchOption(RubberBandStretcher::Options options) { From ac4072937e42398c70de7ef059386650432f8272 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 13 Jun 2022 16:06:21 +0100 Subject: [PATCH 102/184] Add process mode; start on key-frame map --- src/RubberBandStretcher.cpp | 5 +- src/finer/R3StretcherImpl.cpp | 133 ++++++++++++++++++++++++++++------ src/finer/R3StretcherImpl.h | 25 ++++++- 3 files changed, 136 insertions(+), 27 deletions(-) diff --git a/src/RubberBandStretcher.cpp b/src/RubberBandStretcher.cpp index 2f8460f..e4a7d43 100644 --- a/src/RubberBandStretcher.cpp +++ b/src/RubberBandStretcher.cpp @@ -140,7 +140,6 @@ void RubberBandStretcher::setPitchOption(Options options) { if (m_d) m_d->setPitchOption(options); - else if (m_r3d) m_r3d->setPitchOption(options); } void @@ -159,7 +158,7 @@ void RubberBandStretcher::setKeyFrameMap(const std::map &mapping) { if (m_d) m_d->setKeyFrameMap(mapping); - //!!! + else m_r3d->setKeyFrameMap(mapping); } size_t @@ -174,7 +173,7 @@ RubberBandStretcher::study(const float *const *input, size_t samples, bool final) { if (m_d) m_d->study(input, samples, final); - //!!! + else m_r3d->study(input, samples, final); } void diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index a20bf0e..50ec3f2 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -42,7 +42,11 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, m_inhop(1), m_prevOuthop(1), m_startSkip(0), - m_draining(false) + m_studyInputDuration(0), + m_totalTargetDuration(0), + m_processInputDuration(0), + m_totalOutputDuration(0), + m_mode(ProcessMode::JustCreated) { double maxClassifierFrequency = 16000.0; if (maxClassifierFrequency > m_parameters.sampleRate/2) { @@ -92,7 +96,7 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, Resampler::Parameters resamplerParameters; resamplerParameters.quality = Resampler::FastestTolerable; - if (m_parameters.options & RubberBandStretcher::OptionProcessRealTime) { + if (isRealTime()) { resamplerParameters.dynamism = Resampler::RatioOftenChanging; resamplerParameters.ratioChange = Resampler::SmoothRatioChange; } else { @@ -123,7 +127,7 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, // introduce more latency, and we don't want gaps when the ratio // changes. - if (!(m_parameters.options & RubberBandStretcher::OptionProcessRealTime)) { + if (!isRealTime()) { int pad = m_guideConfiguration.longestFftSize / 2; for (int c = 0; c < m_parameters.channels; ++c) { m_channelData[c]->inbuf->zero(pad); @@ -163,6 +167,15 @@ R3StretcherImpl::ScaleData::synthesisWindowLength(int fftSize) void R3StretcherImpl::setTimeRatio(double ratio) { + if (!isRealTime()) { + if (m_mode == ProcessMode::Studying || + m_mode == ProcessMode::Processing) { + m_parameters.logger("R3StretcherImpl::setTimeRatio: Cannot set time ratio while studying or processing in non-RT mode"); + return; + } + } + + if (ratio == m_timeRatio) return; m_timeRatio = ratio; calculateHop(); } @@ -170,6 +183,15 @@ R3StretcherImpl::setTimeRatio(double ratio) void R3StretcherImpl::setPitchScale(double scale) { + if (!isRealTime()) { + if (m_mode == ProcessMode::Studying || + m_mode == ProcessMode::Processing) { + m_parameters.logger("R3StretcherImpl::setTimeRatio: Cannot set pitch scale while studying or processing in non-RT mode"); + return; + } + } + + if (scale == m_pitchScale) return; m_pitchScale = scale; calculateHop(); } @@ -177,6 +199,14 @@ R3StretcherImpl::setPitchScale(double scale) void R3StretcherImpl::setFormantScale(double scale) { + if (!isRealTime()) { + if (m_mode == ProcessMode::Studying || + m_mode == ProcessMode::Processing) { + m_parameters.logger("R3StretcherImpl::setTimeRatio: Cannot set formant scale while studying or processing in non-RT mode"); + return; + } + } + m_formantScale = scale; } @@ -190,16 +220,19 @@ R3StretcherImpl::setFormantOption(RubberBandStretcher::Options options) m_parameters.options |= options; } -//!!! unused? void -R3StretcherImpl::setPitchOption(RubberBandStretcher::Options options) +R3StretcherImpl::setKeyFrameMap(const std::map &mapping) { - int mask = (RubberBandStretcher::OptionPitchHighQuality | - RubberBandStretcher::OptionPitchHighSpeed | - RubberBandStretcher::OptionPitchHighConsistency); - m_parameters.options &= ~mask; - options &= mask; - m_parameters.options |= options; + if (isRealTime()) { + m_parameters.logger("R3StretcherImpl::setKeyFrameMap: Cannot specify key frame map in RT mode"); + return; + } + if (m_mode == ProcessMode::Processing || m_mode == ProcessMode::Finished) { + m_parameters.logger("R3StretcherImpl::setKeyFrameMap: Cannot specify key frame map after process() has begun"); + return; + } + + m_keyFrameMap = mapping; } void @@ -229,6 +262,14 @@ R3StretcherImpl::calculateHop() // std::cout << "R3StretcherImpl::calculateHop: inhop = " << m_inhop << ", proposed outhop = " << proposedOuthop << ", mean outhop = " << m_inhop * ratio << std::endl; } +void +R3StretcherImpl::updateRatioFromMap() +{ + if (m_keyFrameMap.empty()) return; + auto itr = m_keyFrameMap.upper_bound(m_processInputDuration); + +} + double R3StretcherImpl::getTimeRatio() const { @@ -250,7 +291,7 @@ R3StretcherImpl::getFormantScale() const size_t R3StretcherImpl::getLatency() const { - if (!(m_parameters.options & RubberBandStretcher::OptionProcessRealTime)) { + if (!isRealTime()) { return 0; } else { double factor = m_pitchScale * 0.5; @@ -280,6 +321,35 @@ R3StretcherImpl::reset() m_prevInhop = m_inhop; m_prevOuthop = int(round(m_inhop * getEffectiveRatio())); + + m_studyInputDuration = 0; + m_totalTargetDuration = 0; + m_processInputDuration = 0; + m_totalOutputDuration = 0; + m_keyFrameMap.clear(); + + m_mode = ProcessMode::JustCreated; +} + +void +R3StretcherImpl::study(const float *const *, size_t samples, bool) +{ + if (isRealTime()) { + m_parameters.logger("R3StretcherImpl::study: Not meaningful in realtime mode"); + return; + } + + if (m_mode == ProcessMode::Processing || m_mode == ProcessMode::Finished) { + m_parameters.logger("R3StretcherImpl::study: Cannot study after processing"); + return; + } + + if (m_mode == ProcessMode::JustCreated) { + m_studyInputDuration = 0; + } + + m_mode = ProcessMode::Studying; + m_studyInputDuration += samples; } size_t @@ -301,10 +371,26 @@ R3StretcherImpl::process(const float *const *input, size_t samples, bool final) { //!!! todo: final -//!!! m_parameters.logger("process called"); + if (m_mode == ProcessMode::Finished) { + m_parameters.logger("R3StretcherImpl::process: Cannot process again after final chunk"); + return; + } + + if (!isRealTime() && !m_keyFrameMap.empty()) { + if (m_mode == ProcessMode::Studying) { + m_totalTargetDuration = + round(m_studyInputDuration * getEffectiveRatio()); + } + } + if (final) { -// m_parameters.logger("final = true"); - m_draining = true; + // 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; } bool allConsumed = false; @@ -324,6 +410,8 @@ R3StretcherImpl::process(const float *const *input, size_t samples, bool final) m_channelData[c]->inbuf->write(input[c], samples); } + m_processInputDuration += samples; + consume(); } @@ -331,17 +419,18 @@ R3StretcherImpl::process(const float *const *input, size_t samples, bool final) int R3StretcherImpl::available() const { -//!!! m_parameters.logger("available called"); int av = int(m_channelData[0]->outbuf->getReadSpace()); - if (av == 0 && m_draining) return -1; - else return av; + if (av == 0 && m_mode == ProcessMode::Finished) { + return -1; + } else { + return av; + } } //!!! __attribute__((annotate("realtime"))) size_t R3StretcherImpl::retrieve(float *const *output, size_t samples) const { -//!!! m_parameters.logger("retrieve called"); size_t got = samples; for (size_t c = 0; c < m_parameters.channels; ++c) { @@ -411,7 +500,7 @@ R3StretcherImpl::consume() int readSpace = m_channelData.at(0)->inbuf->getReadSpace(); if (readSpace < longest) { - if (m_draining) { + if (m_mode == ProcessMode::Finished) { if (readSpace == 0) { break; } @@ -475,7 +564,7 @@ R3StretcherImpl::consume() m_channelAssembly.mixdown.data(), outhop, 1.0 / m_pitchScale, - m_draining && readSpace < longest); + m_mode == ProcessMode::Finished && readSpace < longest); } // Emit @@ -490,7 +579,7 @@ R3StretcherImpl::consume() int readSpace = cd->inbuf->getReadSpace(); if (readSpace < inhop) { - // This should happen only when draining + // This should happen only when draining (Finished) cd->inbuf->skip(readSpace); } else { cd->inbuf->skip(inhop); diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 8d2181f..e20794a 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -75,9 +75,11 @@ public: double getPitchScale() const; double getFormantScale() const; + void setKeyFrameMap(const std::map &); + void setFormantOption(RubberBandStretcher::Options); - void setPitchOption(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; @@ -280,10 +282,24 @@ protected: int m_prevInhop; int m_prevOuthop; int m_startSkip; - bool m_draining; + + size_t m_studyInputDuration; + size_t m_totalTargetDuration; + size_t m_processInputDuration; + size_t m_totalOutputDuration; + std::map m_keyFrameMap; + + enum class ProcessMode { + JustCreated, + Studying, + Processing, + Finished + }; + ProcessMode m_mode; void consume(); void calculateHop(); + void updateRatioFromMap(); void analyseChannel(int channel, int inhop, int prevInhop, int prevOuthop); void analyseFormant(int channel); void adjustFormant(int channel); @@ -293,6 +309,11 @@ protected: double getEffectiveRatio() const { return m_timeRatio * m_pitchScale; } + + bool isRealTime() const { + return m_parameters.options & + RubberBandStretcher::OptionProcessRealTime; + } static void logCout(const std::string &message) { std::cout << "RubberBandStretcher: " << message << std::endl; From 9dbf53026a10738888c0c7fd2d2e5c9f323ac650 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 13 Jun 2022 17:16:03 +0100 Subject: [PATCH 103/184] Fix failure to calculate offline stretch correctly when a zero-chunk region is found; provide alternate projection calculation method for R3 without stop-the-world phase resets --- src/common/StretchCalculator.cpp | 30 ++++++++++++++++++++++-------- src/common/StretchCalculator.h | 3 ++- src/faster/StretcherProcess.cpp | 2 +- src/finer/R3StretcherImpl.cpp | 11 ++++++++--- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/common/StretchCalculator.cpp b/src/common/StretchCalculator.cpp index 2ad0eea..1f3948e 100644 --- a/src/common/StretchCalculator.cpp +++ b/src/common/StretchCalculator.cpp @@ -141,10 +141,16 @@ StretchCalculator::calculate(double ratio, size_t inputDuration, size_t regionDuration = regionEnd - regionStart; size_t nchunks = regionEndChunk - regionStartChunk; + + if (m_debugLevel > 1) { + std::cerr << "region from " << regionStartChunk << " to " << regionEndChunk << " (samples " << regionStart << " to " << regionEnd << ")" << std::endl; + } if (nchunks == 0) { - //!!! - break; + if (m_debugLevel > 1) { + std::cerr << "note: nchunks == 0" << std::endl; + } + continue; } double per = double(regionDuration) / double(nchunks); @@ -335,7 +341,8 @@ StretchCalculator::calculateSingle(double timeRatio, float df, size_t inIncrement, size_t analysisWindowSize, - size_t synthesisWindowSize) + size_t synthesisWindowSize, + bool alignFrameStarts) { double ratio = timeRatio / effectivePitchRatio; @@ -413,11 +420,18 @@ StretchCalculator::calculateSingle(double timeRatio, 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 intended, projected; + if (alignFrameStarts) { // R3 + intended = expectedOutFrame(m_inFrameCounter, timeRatio); + projected = 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; diff --git a/src/common/StretchCalculator.h b/src/common/StretchCalculator.h index f7a8112..e1f91fc 100644 --- a/src/common/StretchCalculator.h +++ b/src/common/StretchCalculator.h @@ -71,7 +71,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; } diff --git a/src/faster/StretcherProcess.cpp b/src/faster/StretcherProcess.cpp index 1a4e823..c870bef 100644 --- a/src/faster/StretcherProcess.cpp +++ b/src/faster/StretcherProcess.cpp @@ -626,7 +626,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); diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 50ec3f2..36b8509 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -128,12 +128,15 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, // changes. if (!isRealTime()) { + m_parameters.logger("Offline mode: pre-padding"); int pad = m_guideConfiguration.longestFftSize / 2; for (int c = 0; c < m_parameters.channels; ++c) { m_channelData[c]->inbuf->zero(pad); } // By the time we skip this later we will have resampled m_startSkip = int(round(pad / m_pitchScale)); + } else { + m_parameters.logger("RT mode: no internal pre-pad"); } } @@ -452,6 +455,7 @@ R3StretcherImpl::consume() int longest = m_guideConfiguration.longestFftSize; int channels = m_parameters.channels; + //!!! todo: wire debug level & logger throughout // m_calculator->setDebugLevel(3); int inhop = m_inhop; @@ -466,8 +470,9 @@ R3StretcherImpl::consume() 1.f, inhop, longest, - 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 @@ -489,7 +494,7 @@ R3StretcherImpl::consume() std::cout << "Note: outhop has changed from " << m_prevOuthop << " to " << outhop << std::endl; } -*/ +*/ while (m_channelData.at(0)->outbuf->getWriteSpace() >= outhop) { // NB our ChannelData, ScaleData, and ChannelScaleData maps From fb75aa36f69e4686a4f98b0c05ab24cac2ab59d0 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 14 Jun 2022 10:01:11 +0100 Subject: [PATCH 104/184] Using the instantaneous ratio here seems more proper, but it causes audible interference when using tiny shift ratios such that the hop flips back and forth between two adjacent values. Let's try using the more stable effective ratio, and see if the window smoothing is sufficient --- src/finer/R3StretcherImpl.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 36b8509..21d13e6 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -814,10 +814,9 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) std::cout << std::endl; } */ - double instantaneousRatio = double(prevOuthop) / double(prevInhop); bool specialCaseUnity = true; - m_guide.updateGuidance(instantaneousRatio, + m_guide.updateGuidance(getEffectiveRatio(), classifyScale->mag.data(), classifyScale->prevMag.data(), cd->readahead.mag.data(), From 33a2696b342664f98af0b571d2517bd2ea333193 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 14 Jun 2022 10:24:08 +0100 Subject: [PATCH 105/184] Update Linux-specific Makefile --- .build.yml | 1 + Doxyfile | 2 +- otherbuilds/Makefile.linux | 278 +++++++++++++++++-------------- rubberband/RubberBandStretcher.h | 4 +- rubberband/rubberband-c.h | 4 +- src/finer/R3StretcherImpl.h | 2 +- 6 files changed, 158 insertions(+), 133 deletions(-) diff --git a/.build.yml b/.build.yml index 5402296..8425bcf 100644 --- a/.build.yml +++ b/.build.yml @@ -18,6 +18,7 @@ tasks: - build: | cd rubberband ninja -C build + meson test -C build ./otherbuilds/check.sh triggers: - action: email 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/otherbuilds/Makefile.linux b/otherbuilds/Makefile.linux index ec361bf..fe52c77 100644 --- a/otherbuilds/Makefile.linux +++ b/otherbuilds/Makefile.linux @@ -25,51 +25,25 @@ 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/StretcherChannelData.cpp \ + src/faster/StretcherImpl.cpp \ + src/faster/StretcherProcess.cpp \ + src/common/Profiler.cpp \ + src/common/Resampler.cpp \ + src/common/FFT.cpp \ + src/common/Allocators.cpp \ + src/common/StretchCalculator.cpp \ + src/common/sysutils.cpp \ + src/common/Thread.cpp \ + src/finer/R3StretcherImpl.cpp LIBRARY_OBJECTS := $(LIBRARY_SOURCES:.cpp=.o) LIBRARY_OBJECTS := $(LIBRARY_OBJECTS:.c=.o) @@ -94,88 +68,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/StretcherImpl.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/sysutils.h +src/RubberBandStretcher.o: src/faster/SincWindow.h src/common/VectorOps.h +src/RubberBandStretcher.o: 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/RubberBandStretcher.o: src/finer/R3StretcherImpl.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/RubberBandStretcher.o: src/common/Resampler.h src/common/FixedVector.h +src/RubberBandStretcher.o: rubberband/RubberBandStretcher.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/StretcherChannelData.o: src/faster/StretcherChannelData.h +src/faster/StretcherChannelData.o: src/faster/StretcherImpl.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/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/StretcherImpl.o: src/faster/StretcherImpl.h +src/faster/StretcherImpl.o: rubberband/RubberBandStretcher.h +src/faster/StretcherImpl.o: src/common/Window.h src/common/sysutils.h +src/faster/StretcherImpl.o: src/common/VectorOps.h src/common/Allocators.h +src/faster/StretcherImpl.o: src/common/FFT.h src/common/RingBuffer.h +src/faster/StretcherImpl.o: src/common/Scavenger.h src/common/Thread.h +src/faster/StretcherImpl.o: src/common/Thread.h src/common/sysutils.h +src/faster/StretcherImpl.o: src/faster/SincWindow.h src/common/VectorOps.h +src/faster/StretcherImpl.o: src/common/Allocators.h +src/faster/StretcherImpl.o: src/faster/CompoundAudioCurve.h +src/faster/StretcherImpl.o: src/faster/PercussiveAudioCurve.h +src/faster/StretcherImpl.o: src/faster/AudioCurveCalculator.h +src/faster/StretcherImpl.o: src/faster/HighFrequencyAudioCurve.h +src/faster/StretcherImpl.o: src/common/SampleFilter.h +src/faster/StretcherImpl.o: src/faster/SilentAudioCurve.h +src/faster/StretcherImpl.o: src/faster/StretcherChannelData.h +src/faster/StretcherImpl.o: src/common/StretchCalculator.h +src/faster/StretcherImpl.o: src/common/Resampler.h src/common/Profiler.h +src/faster/StretcherProcess.o: src/faster/StretcherImpl.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/sysutils.h +src/faster/StretcherProcess.o: src/faster/SincWindow.h src/common/VectorOps.h +src/faster/StretcherProcess.o: 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/Resampler.h src/common/Profiler.h +src/faster/StretcherProcess.o: src/common/mathmisc.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/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/Allocators.o: src/common/Allocators.h src/common/VectorOps.h +src/common/Allocators.o: src/common/sysutils.h +src/common/StretchCalculator.o: src/common/StretchCalculator.h +src/common/StretchCalculator.o: src/common/sysutils.h +src/common/sysutils.o: src/common/sysutils.h +src/common/Thread.o: src/common/Thread.h +src/finer/R3StretcherImpl.o: src/finer/R3StretcherImpl.h +src/finer/R3StretcherImpl.o: src/finer/BinSegmenter.h +src/finer/R3StretcherImpl.o: src/finer/BinClassifier.h +src/finer/R3StretcherImpl.o: src/common/Allocators.h +src/finer/R3StretcherImpl.o: src/common/MovingMedian.h +src/finer/R3StretcherImpl.o: src/common/SampleFilter.h +src/finer/R3StretcherImpl.o: src/common/FixedVector.h src/common/Allocators.h +src/finer/R3StretcherImpl.o: src/common/VectorOps.h src/common/sysutils.h +src/finer/R3StretcherImpl.o: src/common/SingleThreadRingBuffer.h +src/finer/R3StretcherImpl.o: src/common/RingBuffer.h +src/finer/R3StretcherImpl.o: src/common/HistogramFilter.h +src/finer/R3StretcherImpl.o: src/common/mathmisc.h src/finer/Guide.h +src/finer/R3StretcherImpl.o: src/finer/Peak.h src/finer/PhaseAdvance.h +src/finer/R3StretcherImpl.o: src/common/StretchCalculator.h +src/finer/R3StretcherImpl.o: src/common/Resampler.h src/common/FFT.h +src/finer/R3StretcherImpl.o: src/common/FixedVector.h src/common/Window.h +src/finer/R3StretcherImpl.o: rubberband/RubberBandStretcher.h +src/finer/R3StretcherImpl.o: src/common/VectorOpsComplex.h diff --git a/rubberband/RubberBandStretcher.h b/rubberband/RubberBandStretcher.h index de4149a..ef81286 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 diff --git a/rubberband/rubberband-c.h b/rubberband/rubberband-c.h index 5172c26..521a531 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 diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index e20794a..635607d 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -36,7 +36,7 @@ #include "../common/Allocators.h" #include "../common/Window.h" -#include "../rubberband/RubberBandStretcher.h" +#include "../../rubberband/RubberBandStretcher.h" #include #include From 638948269bc0851267671ba1917bc2d4bb962534 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 14 Jun 2022 13:59:17 +0100 Subject: [PATCH 106/184] Remove Silent classification from BinClassifier: not only is it not very useful, it's misaligned with the other classifications because it doesn't account for lag, and so it can make those wrong --- .appveyor.yml | 1 + .github/workflows/macos-ios.yml | 2 + meson.build | 1 + src/finer/BinClassifier.h | 18 ++-- src/finer/BinSegmenter.h | 27 ++--- src/finer/R3StretcherImpl.cpp | 4 +- src/finer/R3StretcherImpl.h | 4 +- src/test/TestBinClassifier.cpp | 186 ++++++++++++++++++++++++++++++++ 8 files changed, 215 insertions(+), 28 deletions(-) create mode 100644 src/test/TestBinClassifier.cpp diff --git a/.appveyor.yml b/.appveyor.yml index 825ae67..8523478 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -16,6 +16,7 @@ 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 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/meson.build b/meson.build index b0eec3b..f37d9e9 100644 --- a/meson.build +++ b/meson.build @@ -90,6 +90,7 @@ unit_test_sources = [ 'src/test/TestVectorOpsComplex.cpp', 'src/test/TestVectorOps.cpp', 'src/test/TestSignalBits.cpp', + 'src/test/TestBinClassifier.cpp', 'src/test/test.cpp', ] diff --git a/src/finer/BinClassifier.h b/src/finer/BinClassifier.h index 350db7c..80069a9 100644 --- a/src/finer/BinClassifier.h +++ b/src/finer/BinClassifier.h @@ -39,8 +39,7 @@ public: enum class Classification { Harmonic = 0, Percussive = 1, - Residual = 2, - Silent = 3 + Residual = 2 }; struct Parameters { @@ -50,18 +49,15 @@ public: int verticalFilterLength; double harmonicThreshold; double percussiveThreshold; - double silenceThreshold; Parameters(int _binCount, int _horizontalFilterLength, int _horizontalFilterLag, int _verticalFilterLength, - double _harmonicThreshold, double _percussiveThreshold, - double _silenceThreshold) : + double _harmonicThreshold, double _percussiveThreshold) : binCount(_binCount), horizontalFilterLength(_horizontalFilterLength), horizontalFilterLag(_horizontalFilterLag), verticalFilterLength(_verticalFilterLength), harmonicThreshold(_harmonicThreshold), - percussiveThreshold(_percussiveThreshold), - silenceThreshold(_silenceThreshold) { } + percussiveThreshold(_percussiveThreshold) { } }; BinClassifier(Parameters parameters) : @@ -118,13 +114,11 @@ public: } double eps = 1.0e-7; - + for (int i = 0; i < n; ++i) { Classification c; - if (mag[i] < m_parameters.silenceThreshold) { - c = Classification::Silent; - } else if (double(m_hf[i]) / (double(m_vf[i]) + eps) > - m_parameters.harmonicThreshold) { + 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) { diff --git a/src/finer/BinSegmenter.h b/src/finer/BinSegmenter.h index c49f03d..705d255 100644 --- a/src/finer/BinSegmenter.h +++ b/src/finer/BinSegmenter.h @@ -50,14 +50,17 @@ public: int fftSize; int binCount; double sampleRate; - Parameters(int _fftSize, int _binCount, double _sampleRate) : - fftSize(_fftSize), binCount(_binCount), sampleRate(_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, 18) + m_classFilter(3, m_parameters.classFilterLength) { } @@ -78,19 +81,19 @@ public: std::cout << "c:"; for (int i = 0; i < n; ++i) { if (i > 0) std::cout << ","; - if (m_numeric[i] == 1) { - std::cout << "1"; - } else { - std::cout << "0"; - } + 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) { - f0 = frequencyForBin - (i, m_parameters.fftSize, m_parameters.sampleRate); + 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; } } @@ -101,7 +104,7 @@ public: for (int i = n - 1; i > 0; --i) { int c = m_numeric[i]; if (!inPercussive) { - if (c == 2) { // residual/silent + if (c == 2) { // residual continue; } else if (c == 1) { // percussive inPercussive = true; diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 21d13e6..8d12558 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -58,10 +58,10 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, BinSegmenter::Parameters segmenterParameters (m_guideConfiguration.classificationFftSize, - classificationBins, m_parameters.sampleRate); + classificationBins, m_parameters.sampleRate, 18); BinClassifier::Parameters classifierParameters - (classificationBins, 9, 1, 10, 2.0, 2.0, 1.0e-7); + (classificationBins, 9, 1, 10, 2.0, 2.0); int inRingBufferSize = m_guideConfiguration.longestFftSize * 2; int outRingBufferSize = m_guideConfiguration.longestFftSize * 16; diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 635607d..730181a 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -193,9 +193,9 @@ protected: haveReadahead(false), classifier(new BinClassifier(classifierParameters)), classification(classifierParameters.binCount, - BinClassifier::Classification::Silent), + BinClassifier::Classification::Residual), nextClassification(classifierParameters.binCount, - BinClassifier::Classification::Silent), + BinClassifier::Classification::Residual), segmenter(new BinSegmenter(segmenterParameters)), segmentation(), prevSegmentation(), nextSegmentation(), mixdown(longestFftSize, 0.f), // though it could be shorter diff --git a/src/test/TestBinClassifier.cpp b/src/test/TestBinClassifier.cpp new file mode 100644 index 0000000..e266287 --- /dev/null +++ b/src/test/TestBinClassifier.cpp @@ -0,0 +1,186 @@ +/* -*- 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. +*/ + +#define BOOST_TEST_DYN_LINK +#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 < 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() + + From 141c314c72bf75e3fa8439ed818114730a305add Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 14 Jun 2022 15:01:44 +0100 Subject: [PATCH 107/184] Proper adjustment of outhop for extreme ratios --- src/finer/Guide.h | 6 +++++ src/finer/R3StretcherImpl.cpp | 43 ++++++++++++++++++++++------------- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/finer/Guide.h b/src/finer/Guide.h index 905e61d..d0027d6 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -138,6 +138,7 @@ public: } void updateGuidance(double ratio, + int outhop, const double *const magnitudes, const double *const prevMagnitudes, const double *const nextMagnitudes, @@ -253,6 +254,11 @@ public: 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); diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 8d12558..a700e85 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -242,27 +242,37 @@ void R3StretcherImpl::calculateHop() { double ratio = getEffectiveRatio(); - double proposedOuthop = 256.0; -//!!! if (proposedOuthop * m_pitchScale > 2048.0) { -// proposedOuthop = 2048.0 / m_pitchScale; -// } - double inhop = 1.0; + + // 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. (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.) - if (ratio > 1.0) { - inhop = proposedOuthop / ratio; - if (inhop < 1.0) { - m_parameters.logger("WARNING: Extreme ratio yields ideal inhop < 1, results may be suspect"); - inhop = 1.0; - } - } else { - inhop = std::min(proposedOuthop / ratio, 340.0); + double proposedOuthop = pow(2.0, 8.0 + 2.0 * log10(ratio)); + if (proposedOuthop > 512.0) proposedOuthop = 512.0; + if (proposedOuthop < 128.0) proposedOuthop = 128.0; + + std::cout << "calculateHop: for ratio " << ratio << " proposedOuthop = " + << proposedOuthop << std::endl; + + double inhop = proposedOuthop / ratio; + if (inhop < 1.0) { + m_parameters.logger("WARNING: Extreme ratio yields ideal inhop < 1, results may be suspect"); + inhop = 1.0; + } + if (inhop > 768.0) { + m_parameters.logger("WARNING: Extreme ratio yields ideal inhop > 768, results may be suspect"); + inhop = 768.0; } m_inhop = int(round(inhop)); - //!!! but if we now have outhop > 4096 ever, we will crash, so we must check - -// std::cout << "R3StretcherImpl::calculateHop: inhop = " << m_inhop << ", proposed outhop = " << proposedOuthop << ", mean outhop = " << m_inhop * ratio << std::endl; + std::cout << "R3StretcherImpl::calculateHop: inhop = " << m_inhop << ", proposed outhop = " << proposedOuthop << ", mean outhop = " << m_inhop * ratio << std::endl; } void @@ -817,6 +827,7 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) bool specialCaseUnity = true; m_guide.updateGuidance(getEffectiveRatio(), + prevOuthop, classifyScale->mag.data(), classifyScale->prevMag.data(), cd->readahead.mag.data(), From b160475b07e6d830fa25852a195f81b9c2071672 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 14 Jun 2022 15:15:55 +0100 Subject: [PATCH 108/184] Fix some compiler warnings --- meson.build | 1 + src/common/HistogramFilter.h | 12 +++++++++--- src/finer/PhaseAdvance.h | 6 +++--- src/finer/R3StretcherImpl.cpp | 14 ++++++-------- src/finer/R3StretcherImpl.h | 4 ++-- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/meson.build b/meson.build index f37d9e9..8a5a0f1 100644 --- a/meson.build +++ b/meson.build @@ -6,6 +6,7 @@ project( license: 'GPL-2.0-or-later', default_options: [ 'cpp_std=c++11', + 'warning_level=3', 'buildtype=release', 'default_library=both', 'b_ndebug=if-release', diff --git a/src/common/HistogramFilter.h b/src/common/HistogramFilter.h index ca4bd64..b14331e 100644 --- a/src/common/HistogramFilter.h +++ b/src/common/HistogramFilter.h @@ -51,10 +51,14 @@ public: 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 < m_histogram.size(); ++i) { + for (int i = 0; i < getNValues(); ++i) { m_histogram[i] = 0; } } @@ -89,7 +93,8 @@ public: int getMedian() const { int half = (m_buffer.getReadSpace() + 1) / 2; int acc = 0; - for (int i = 0; i < m_histogram.size(); ++i) { + int nvalues = getNValues(); + for (int i = 0; i < nvalues; ++i) { acc += m_histogram[i]; if (acc >= half) { return i; @@ -108,7 +113,8 @@ public: } int max = 0; int mode = 0; - for (int i = 0; i < m_histogram.size(); ++i) { + int nvalues = getNValues(); + for (int i = 0; i < nvalues; ++i) { int h = m_histogram[i]; if (i == 0 || h > max) { max = h; diff --git a/src/finer/PhaseAdvance.h b/src/finer/PhaseAdvance.h index c20cf1d..ecacd54 100644 --- a/src/finer/PhaseAdvance.h +++ b/src/finer/PhaseAdvance.h @@ -53,7 +53,7 @@ public: m_binCount(parameters.fftSize / 2 + 1), m_peakPicker(m_binCount), m_reported(false) { - size_t ch = m_parameters.channels; + 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); @@ -69,7 +69,7 @@ public: } ~GuidedPhaseAdvance() { - size_t ch = m_parameters.channels; + int ch = m_parameters.channels; deallocate_channels(m_currentPeaks, ch); deallocate_channels(m_prevPeaks, ch); deallocate(m_greatestChannel); @@ -79,7 +79,7 @@ public: } void reset() { - size_t ch = m_parameters.channels; + 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); diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index a700e85..af605a1 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -279,7 +279,7 @@ void R3StretcherImpl::updateRatioFromMap() { if (m_keyFrameMap.empty()) return; - auto itr = m_keyFrameMap.upper_bound(m_processInputDuration); +//!!! auto itr = m_keyFrameMap.upper_bound(m_processInputDuration); } @@ -370,7 +370,7 @@ R3StretcherImpl::getSamplesRequired() const { if (available() != 0) return 0; int longest = m_guideConfiguration.longestFftSize; - size_t rs = m_channelData[0]->inbuf->getReadSpace(); + int rs = m_channelData[0]->inbuf->getReadSpace(); if (rs < longest) { return longest - rs; } else { @@ -406,8 +406,6 @@ R3StretcherImpl::process(const float *const *input, size_t samples, bool final) m_mode = ProcessMode::Processing; } - bool allConsumed = false; - size_t ws = m_channelData[0]->inbuf->getWriteSpace(); if (samples > ws) { //!!! check this @@ -444,15 +442,15 @@ R3StretcherImpl::available() const size_t R3StretcherImpl::retrieve(float *const *output, size_t samples) const { - size_t got = samples; + int got = samples; - for (size_t c = 0; c < m_parameters.channels; ++c) { - size_t gotHere = m_channelData[c]->outbuf->read(output[c], got); + 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_parameters.logger("R3StretcherImpl::retrieve: WARNING: channel imbalance detected"); } - got = gotHere; + got = std::min(got, std::max(gotHere, 0)); } } diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 730181a..198e03a 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -248,8 +248,8 @@ protected: analysisWindowLength(fftSize)), synthesisWindow(synthesisWindowShape(fftSize), synthesisWindowLength(fftSize)), - guided(guidedParameters), - windowScaleFactor(0.0) + windowScaleFactor(0.0), + guided(guidedParameters) { int asz = analysisWindow.getSize(), ssz = synthesisWindow.getSize(); int off = (asz - ssz) / 2; From b86a4df1522108b7a8057cfe23b680e527cb3267 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 14 Jun 2022 15:31:21 +0100 Subject: [PATCH 109/184] Update macOS/iOS custom Makefiles --- otherbuilds/Makefile.ios | 76 ++----- otherbuilds/Makefile.linux | 4 +- otherbuilds/Makefile.macos | 273 ++++++++++++++------------ otherbuilds/Makefile.macos-universal | 283 +++++++++++++++------------ 4 files changed, 333 insertions(+), 303 deletions(-) diff --git a/otherbuilds/Makefile.ios b/otherbuilds/Makefile.ios index f6b8abe..6a5e67c 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,25 @@ 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/StretcherChannelData.cpp \ + src/faster/StretcherImpl.cpp \ + src/faster/StretcherProcess.cpp \ + src/common/Profiler.cpp \ + src/common/Resampler.cpp \ + src/common/FFT.cpp \ + src/common/Allocators.cpp \ + src/common/StretchCalculator.cpp \ + src/common/sysutils.cpp \ + src/common/Thread.cpp \ + src/finer/R3StretcherImpl.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 fe52c77..7ca5470 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) diff --git a/otherbuilds/Makefile.macos b/otherbuilds/Makefile.macos index eae185f..1bd1184 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,25 @@ 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/StretcherChannelData.cpp \ + src/faster/StretcherImpl.cpp \ + src/faster/StretcherProcess.cpp \ + src/common/Profiler.cpp \ + src/common/Resampler.cpp \ + src/common/FFT.cpp \ + src/common/Allocators.cpp \ + src/common/StretchCalculator.cpp \ + src/common/sysutils.cpp \ + src/common/Thread.cpp \ + src/finer/R3StretcherImpl.cpp LIBRARY_OBJECTS := $(LIBRARY_SOURCES:.cpp=.o) LIBRARY_OBJECTS := $(LIBRARY_OBJECTS:.c=.o) @@ -103,71 +69,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/StretcherImpl.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/sysutils.h +src/RubberBandStretcher.o: src/faster/SincWindow.h src/common/VectorOps.h +src/RubberBandStretcher.o: 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/RubberBandStretcher.o: src/finer/R3StretcherImpl.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/RubberBandStretcher.o: src/common/Resampler.h src/common/FixedVector.h +src/RubberBandStretcher.o: rubberband/RubberBandStretcher.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/StretcherChannelData.o: src/faster/StretcherChannelData.h +src/faster/StretcherChannelData.o: src/faster/StretcherImpl.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/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/StretcherImpl.o: src/faster/StretcherImpl.h +src/faster/StretcherImpl.o: rubberband/RubberBandStretcher.h +src/faster/StretcherImpl.o: src/common/Window.h src/common/sysutils.h +src/faster/StretcherImpl.o: src/common/VectorOps.h src/common/Allocators.h +src/faster/StretcherImpl.o: src/common/FFT.h src/common/RingBuffer.h +src/faster/StretcherImpl.o: src/common/Scavenger.h src/common/Thread.h +src/faster/StretcherImpl.o: src/common/Thread.h src/common/sysutils.h +src/faster/StretcherImpl.o: src/faster/SincWindow.h src/common/VectorOps.h +src/faster/StretcherImpl.o: src/common/Allocators.h +src/faster/StretcherImpl.o: src/faster/CompoundAudioCurve.h +src/faster/StretcherImpl.o: src/faster/PercussiveAudioCurve.h +src/faster/StretcherImpl.o: src/faster/AudioCurveCalculator.h +src/faster/StretcherImpl.o: src/faster/HighFrequencyAudioCurve.h +src/faster/StretcherImpl.o: src/common/SampleFilter.h +src/faster/StretcherImpl.o: src/faster/SilentAudioCurve.h +src/faster/StretcherImpl.o: src/faster/StretcherChannelData.h +src/faster/StretcherImpl.o: src/common/StretchCalculator.h +src/faster/StretcherImpl.o: src/common/Resampler.h src/common/Profiler.h +src/faster/StretcherProcess.o: src/faster/StretcherImpl.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/sysutils.h +src/faster/StretcherProcess.o: src/faster/SincWindow.h src/common/VectorOps.h +src/faster/StretcherProcess.o: 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/Resampler.h src/common/Profiler.h +src/faster/StretcherProcess.o: src/common/mathmisc.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/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/Allocators.o: src/common/Allocators.h src/common/VectorOps.h +src/common/Allocators.o: src/common/sysutils.h +src/common/StretchCalculator.o: src/common/StretchCalculator.h +src/common/StretchCalculator.o: src/common/sysutils.h +src/common/sysutils.o: src/common/sysutils.h +src/common/Thread.o: src/common/Thread.h +src/finer/R3StretcherImpl.o: src/finer/R3StretcherImpl.h +src/finer/R3StretcherImpl.o: src/finer/BinSegmenter.h +src/finer/R3StretcherImpl.o: src/finer/BinClassifier.h +src/finer/R3StretcherImpl.o: src/common/Allocators.h +src/finer/R3StretcherImpl.o: src/common/MovingMedian.h +src/finer/R3StretcherImpl.o: src/common/SampleFilter.h +src/finer/R3StretcherImpl.o: src/common/FixedVector.h src/common/Allocators.h +src/finer/R3StretcherImpl.o: src/common/VectorOps.h src/common/sysutils.h +src/finer/R3StretcherImpl.o: src/common/SingleThreadRingBuffer.h +src/finer/R3StretcherImpl.o: src/common/RingBuffer.h +src/finer/R3StretcherImpl.o: src/common/HistogramFilter.h +src/finer/R3StretcherImpl.o: src/common/mathmisc.h src/finer/Guide.h +src/finer/R3StretcherImpl.o: src/finer/Peak.h src/finer/PhaseAdvance.h +src/finer/R3StretcherImpl.o: src/common/StretchCalculator.h +src/finer/R3StretcherImpl.o: src/common/Resampler.h src/common/FFT.h +src/finer/R3StretcherImpl.o: src/common/FixedVector.h src/common/Window.h +src/finer/R3StretcherImpl.o: rubberband/RubberBandStretcher.h +src/finer/R3StretcherImpl.o: src/common/VectorOpsComplex.h diff --git a/otherbuilds/Makefile.macos-universal b/otherbuilds/Makefile.macos-universal index aa03954..0b32b75 100644 --- a/otherbuilds/Makefile.macos-universal +++ b/otherbuilds/Makefile.macos-universal @@ -1,85 +1,49 @@ -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/StretcherChannelData.cpp \ + src/faster/StretcherImpl.cpp \ + src/faster/StretcherProcess.cpp \ + src/common/Profiler.cpp \ + src/common/Resampler.cpp \ + src/common/FFT.cpp \ + src/common/Allocators.cpp \ + src/common/StretchCalculator.cpp \ + src/common/sysutils.cpp \ + src/common/Thread.cpp \ + src/finer/R3StretcherImpl.cpp LIBRARY_OBJECTS := $(LIBRARY_SOURCES:.cpp=.o) LIBRARY_OBJECTS := $(LIBRARY_OBJECTS:.c=.o) @@ -105,71 +69,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/StretcherImpl.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/sysutils.h +src/RubberBandStretcher.o: src/faster/SincWindow.h src/common/VectorOps.h +src/RubberBandStretcher.o: 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/RubberBandStretcher.o: src/finer/R3StretcherImpl.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/RubberBandStretcher.o: src/common/Resampler.h src/common/FixedVector.h +src/RubberBandStretcher.o: rubberband/RubberBandStretcher.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/StretcherChannelData.o: src/faster/StretcherChannelData.h +src/faster/StretcherChannelData.o: src/faster/StretcherImpl.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/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/StretcherImpl.o: src/faster/StretcherImpl.h +src/faster/StretcherImpl.o: rubberband/RubberBandStretcher.h +src/faster/StretcherImpl.o: src/common/Window.h src/common/sysutils.h +src/faster/StretcherImpl.o: src/common/VectorOps.h src/common/Allocators.h +src/faster/StretcherImpl.o: src/common/FFT.h src/common/RingBuffer.h +src/faster/StretcherImpl.o: src/common/Scavenger.h src/common/Thread.h +src/faster/StretcherImpl.o: src/common/Thread.h src/common/sysutils.h +src/faster/StretcherImpl.o: src/faster/SincWindow.h src/common/VectorOps.h +src/faster/StretcherImpl.o: src/common/Allocators.h +src/faster/StretcherImpl.o: src/faster/CompoundAudioCurve.h +src/faster/StretcherImpl.o: src/faster/PercussiveAudioCurve.h +src/faster/StretcherImpl.o: src/faster/AudioCurveCalculator.h +src/faster/StretcherImpl.o: src/faster/HighFrequencyAudioCurve.h +src/faster/StretcherImpl.o: src/common/SampleFilter.h +src/faster/StretcherImpl.o: src/faster/SilentAudioCurve.h +src/faster/StretcherImpl.o: src/faster/StretcherChannelData.h +src/faster/StretcherImpl.o: src/common/StretchCalculator.h +src/faster/StretcherImpl.o: src/common/Resampler.h src/common/Profiler.h +src/faster/StretcherProcess.o: src/faster/StretcherImpl.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/sysutils.h +src/faster/StretcherProcess.o: src/faster/SincWindow.h src/common/VectorOps.h +src/faster/StretcherProcess.o: 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/Resampler.h src/common/Profiler.h +src/faster/StretcherProcess.o: src/common/mathmisc.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/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/Allocators.o: src/common/Allocators.h src/common/VectorOps.h +src/common/Allocators.o: src/common/sysutils.h +src/common/StretchCalculator.o: src/common/StretchCalculator.h +src/common/StretchCalculator.o: src/common/sysutils.h +src/common/sysutils.o: src/common/sysutils.h +src/common/Thread.o: src/common/Thread.h +src/finer/R3StretcherImpl.o: src/finer/R3StretcherImpl.h +src/finer/R3StretcherImpl.o: src/finer/BinSegmenter.h +src/finer/R3StretcherImpl.o: src/finer/BinClassifier.h +src/finer/R3StretcherImpl.o: src/common/Allocators.h +src/finer/R3StretcherImpl.o: src/common/MovingMedian.h +src/finer/R3StretcherImpl.o: src/common/SampleFilter.h +src/finer/R3StretcherImpl.o: src/common/FixedVector.h src/common/Allocators.h +src/finer/R3StretcherImpl.o: src/common/VectorOps.h src/common/sysutils.h +src/finer/R3StretcherImpl.o: src/common/SingleThreadRingBuffer.h +src/finer/R3StretcherImpl.o: src/common/RingBuffer.h +src/finer/R3StretcherImpl.o: src/common/HistogramFilter.h +src/finer/R3StretcherImpl.o: src/common/mathmisc.h src/finer/Guide.h +src/finer/R3StretcherImpl.o: src/finer/Peak.h src/finer/PhaseAdvance.h +src/finer/R3StretcherImpl.o: src/common/StretchCalculator.h +src/finer/R3StretcherImpl.o: src/common/Resampler.h src/common/FFT.h +src/finer/R3StretcherImpl.o: src/common/FixedVector.h src/common/Window.h +src/finer/R3StretcherImpl.o: rubberband/RubberBandStretcher.h +src/finer/R3StretcherImpl.o: src/common/VectorOpsComplex.h From 9ddb6b370aea517e25ccc7875fa1578d14ea2a4f Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 14 Jun 2022 16:47:06 +0100 Subject: [PATCH 110/184] Windows build fix --- main/main.cpp | 5 ++++- meson.build | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/main/main.cpp b/main/main.cpp index 265a52d..16e559d 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -49,7 +49,10 @@ 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 diff --git a/meson.build b/meson.build index 8a5a0f1..5dd0492 100644 --- a/meson.build +++ b/meson.build @@ -66,8 +66,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 From 2c57d0ee30a749c616defd0307b8533db7496311 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 14 Jun 2022 16:52:09 +0100 Subject: [PATCH 111/184] Fix compiler warnings --- src/RubberBandStretcher.cpp | 2 +- src/common/MovingMedian.h | 2 +- src/common/Window.h | 12 ++++++------ src/finer/R3StretcherImpl.cpp | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/RubberBandStretcher.cpp b/src/RubberBandStretcher.cpp index e4a7d43..8e62e33 100644 --- a/src/RubberBandStretcher.cpp +++ b/src/RubberBandStretcher.cpp @@ -42,7 +42,7 @@ RubberBandStretcher::RubberBandStretcher(size_t sampleRate, m_r3d ((options & OptionEngineFiner) ? new R3StretcherImpl(R3StretcherImpl::Parameters - (sampleRate, channels, options), + (double(sampleRate), channels, options), initialTimeRatio, initialPitchScale) : nullptr) { diff --git a/src/common/MovingMedian.h b/src/common/MovingMedian.h index 48fcd39..e7ede71 100644 --- a/src/common/MovingMedian.h +++ b/src/common/MovingMedian.h @@ -97,7 +97,7 @@ public: if (m_percentile == fifty) { // exact default value return m_sortspace[n / 2]; } else { - int index = float(n) * m_percentile / 100.f; + int index = int(floorf(float(n) * m_percentile / 100.f)); if (index >= m_fill) index = m_fill - 1; return m_sortspace[index]; } diff --git a/src/common/Window.h b/src/common/Window.h index 58a2eeb..2800c7e 100644 --- a/src/common/Window.h +++ b/src/common/Window.h @@ -111,7 +111,7 @@ protected: T m_area; void encache(); - void cosinewin(T *, T, T, T, T); + void cosinewin(T *, double, double, double, double); }; template @@ -248,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/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index af605a1..c0b7412 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -392,7 +392,7 @@ R3StretcherImpl::process(const float *const *input, size_t samples, bool final) if (!isRealTime() && !m_keyFrameMap.empty()) { if (m_mode == ProcessMode::Studying) { m_totalTargetDuration = - round(m_studyInputDuration * getEffectiveRatio()); + size_t(round(m_studyInputDuration * getEffectiveRatio())); } } From bbb5db3087818758aab9303b6a74cc91fea0981b Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 14 Jun 2022 17:03:33 +0100 Subject: [PATCH 112/184] Windows dedicated build updates --- dotnet/rubberband-library.vcxproj | 72 ++++++++------------------ otherbuilds/rubberband-library.vcxproj | 72 ++++++++------------------ src/common/StretchCalculator.cpp | 3 +- 3 files changed, 44 insertions(+), 103 deletions(-) diff --git a/dotnet/rubberband-library.vcxproj b/dotnet/rubberband-library.vcxproj index 8fbddaf..51a2c33 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,25 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/otherbuilds/rubberband-library.vcxproj b/otherbuilds/rubberband-library.vcxproj index 8fbddaf..51a2c33 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,25 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/src/common/StretchCalculator.cpp b/src/common/StretchCalculator.cpp index 1f3948e..25c440f 100644 --- a/src/common/StretchCalculator.cpp +++ b/src/common/StretchCalculator.cpp @@ -424,7 +424,8 @@ StretchCalculator::calculateSingle(double timeRatio, int64_t intended, projected; if (alignFrameStarts) { // R3 intended = expectedOutFrame(m_inFrameCounter, timeRatio); - projected = m_outFrameCounter; + projected = + int64_t(round(m_outFrameCounter)); } else { // R2 intended = expectedOutFrame (m_inFrameCounter + analysisWindowSize/4, timeRatio); From eb7e17164351e0bccfd45c69d9a57c24945fd0e0 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 14 Jun 2022 17:56:13 +0100 Subject: [PATCH 113/184] Further build updates --- otherbuilds/Android.mk | 56 +++++++++++----------------- otherbuilds/Makefile.ios | 1 + otherbuilds/Makefile.linux | 1 + otherbuilds/Makefile.macos | 1 + otherbuilds/Makefile.macos-universal | 1 + otherbuilds/check.sh | 10 ++--- 6 files changed, 30 insertions(+), 40 deletions(-) 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 6a5e67c..938435a 100644 --- a/otherbuilds/Makefile.ios +++ b/otherbuilds/Makefile.ios @@ -47,6 +47,7 @@ LIBRARY_SOURCES := \ src/faster/StretcherChannelData.cpp \ src/faster/StretcherImpl.cpp \ src/faster/StretcherProcess.cpp \ + src/common/BQResampler.cpp \ src/common/Profiler.cpp \ src/common/Resampler.cpp \ src/common/FFT.cpp \ diff --git a/otherbuilds/Makefile.linux b/otherbuilds/Makefile.linux index 7ca5470..e721c4b 100644 --- a/otherbuilds/Makefile.linux +++ b/otherbuilds/Makefile.linux @@ -36,6 +36,7 @@ LIBRARY_SOURCES := \ src/faster/StretcherChannelData.cpp \ src/faster/StretcherImpl.cpp \ src/faster/StretcherProcess.cpp \ + src/common/BQResampler.cpp \ src/common/Profiler.cpp \ src/common/Resampler.cpp \ src/common/FFT.cpp \ diff --git a/otherbuilds/Makefile.macos b/otherbuilds/Makefile.macos index 1bd1184..db68eae 100644 --- a/otherbuilds/Makefile.macos +++ b/otherbuilds/Makefile.macos @@ -36,6 +36,7 @@ LIBRARY_SOURCES := \ src/faster/StretcherChannelData.cpp \ src/faster/StretcherImpl.cpp \ src/faster/StretcherProcess.cpp \ + src/common/BQResampler.cpp \ src/common/Profiler.cpp \ src/common/Resampler.cpp \ src/common/FFT.cpp \ diff --git a/otherbuilds/Makefile.macos-universal b/otherbuilds/Makefile.macos-universal index 0b32b75..69e6180 100644 --- a/otherbuilds/Makefile.macos-universal +++ b/otherbuilds/Makefile.macos-universal @@ -36,6 +36,7 @@ LIBRARY_SOURCES := \ src/faster/StretcherChannelData.cpp \ src/faster/StretcherImpl.cpp \ src/faster/StretcherProcess.cpp \ + src/common/BQResampler.cpp \ src/common/Profiler.cpp \ src/common/Resampler.cpp \ src/common/FFT.cpp \ diff --git a/otherbuilds/check.sh b/otherbuilds/check.sh index 0a3c3e5..ed495a0 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++ 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 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++ 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 main/main.cpp single/RubberBandSingle.cpp -o test_single -lsndfile -framework Accelerate echo " *** Running build from single-file source" ./test_single -V From 136c546955f4c538b3c2af690ac1ccb2d9cd2fe4 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 15 Jun 2022 09:39:36 +0100 Subject: [PATCH 114/184] We need the full range for this FFT now, since we are switching to it of necessity when outhop > 256 --- src/finer/Guide.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/finer/Guide.h b/src/finer/Guide.h index d0027d6..9ffa63b 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -124,9 +124,11 @@ public: m_configuration.fftBandLimits[0] = BandLimits(bandFftSize, rate, 0.0, m_maxLower); + // This is the classification and fallback FFT: we need the + // full range for it bandFftSize = roundUp(int(ceil(rate/32.0))); m_configuration.fftBandLimits[1] = - BandLimits(bandFftSize, rate, 0.0, m_maxHigher); + BandLimits(bandFftSize, rate, 0.0, rate / 2.0); bandFftSize = roundUp(int(ceil(rate/64.0))); m_configuration.fftBandLimits[2] = From a61a4f3201dfd81cff5f00bad808f1453616b88e Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 15 Jun 2022 09:40:09 +0100 Subject: [PATCH 115/184] Avoid dropping the 1024-point FFT until at least ratio 1.5 --- src/finer/R3StretcherImpl.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index c0b7412..cb972ff 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -248,12 +248,18 @@ R3StretcherImpl::calculateHop() // 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. (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 = pow(2.0, 8.0 + 2.0 * log10(ratio)); + // 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; From 3db535281b0ea4a063e9dff124c366ed6854b843 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 15 Jun 2022 10:00:49 +0100 Subject: [PATCH 116/184] This must round down; otherwise we can end up with the outhop exceeding 256 for some hops when we haven't planned it --- src/finer/R3StretcherImpl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index cb972ff..5f9ffeb 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -276,7 +276,7 @@ R3StretcherImpl::calculateHop() inhop = 768.0; } - m_inhop = int(round(inhop)); + m_inhop = int(floor(inhop)); std::cout << "R3StretcherImpl::calculateHop: inhop = " << m_inhop << ", proposed outhop = " << proposedOuthop << ", mean outhop = " << m_inhop * ratio << std::endl; } From 1330c8d4b6995d5a9946b269c9747a11e9216ef1 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 15 Jun 2022 10:17:43 +0100 Subject: [PATCH 117/184] Add tests for p=1 and p=0 --- src/test/TestSignalBits.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/test/TestSignalBits.cpp b/src/test/TestSignalBits.cpp index 8c6e106..bd56248 100644 --- a/src/test/TestSignalBits.cpp +++ b/src/test/TestSignalBits.cpp @@ -227,6 +227,28 @@ BOOST_AUTO_TEST_CASE(peakpick_nearest_2_12) 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); From 291271d532f6d12accf577cac85440b03cdb9698 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 15 Jun 2022 10:39:41 +0100 Subject: [PATCH 118/184] Loosen locking gradually as the ratio increases --- src/finer/Guide.h | 47 ++++++++++++++++++++++++++++++---------- src/finer/PhaseAdvance.h | 2 +- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/finer/Guide.h b/src/finer/Guide.h index 9ffa63b..74058e6 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -66,10 +66,10 @@ public: struct Guidance { FftBand fftBands[3]; - PhaseLockBand phaseLockBands[5]; + PhaseLockBand phaseLockBands[4]; Range kick; Range preKick; - Range highPercussive; + Range highUnlocked; Range phaseReset; Range channelLock; }; @@ -152,7 +152,7 @@ public: guidance.kick.present = false; guidance.preKick.present = false; - guidance.highPercussive.present = false; + guidance.highUnlocked.present = false; guidance.phaseReset.present = false; double nyquist = m_parameters.sampleRate / 2.0; @@ -168,7 +168,9 @@ public: guidance.fftBands[1].f1 = m_minHigher; guidance.fftBands[2].f0 = m_minHigher; guidance.fftBands[2].f1 = nyquist; - for (int i = 0; i < 5; ++i) { + for (int i = 0; i < + sizeof(guidance.phaseLockBands) / + sizeof(guidance.phaseLockBands[0]); ++i) { guidance.phaseLockBands[i].p = 0; guidance.phaseLockBands[i].beta = 1.0; guidance.phaseLockBands[i].f0 = nyquist; @@ -214,9 +216,9 @@ public: } if (segmentation.residualAbove > segmentation.percussiveAbove) { - guidance.highPercussive.present = true; - guidance.highPercussive.f0 = segmentation.percussiveAbove; - guidance.highPercussive.f1 = segmentation.residualAbove; + guidance.highUnlocked.present = true; + guidance.highUnlocked.f0 = segmentation.percussiveAbove; + guidance.highUnlocked.f1 = segmentation.residualAbove; } double bigGap = 4000.0; @@ -283,12 +285,33 @@ public: 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; + } - // Currently unused - guidance.phaseLockBands[4].p = 0; - guidance.phaseLockBands[4].beta = 1.0; - guidance.phaseLockBands[4].f0 = nyquist; - guidance.phaseLockBands[4].f1 = nyquist; + 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; diff --git a/src/finer/PhaseAdvance.h b/src/finer/PhaseAdvance.h index ecacd54..0808699 100644 --- a/src/finer/PhaseAdvance.h +++ b/src/finer/PhaseAdvance.h @@ -190,7 +190,7 @@ public: ph = m_unlocked[c][i]; } else if (inRange(f, g->phaseReset) || inRange(f, g->kick)) { ph = phase[c][i]; - } else if (inRange (f, g->highPercussive)) { + } else if (inRange (f, g->highUnlocked)) { ph = m_unlocked[c][i]; } else { int peak = m_currentPeaks[c][i]; From f3dfada88829ddd4f2a8e2ee93649e2b1937b1b3 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 15 Jun 2022 11:31:35 +0100 Subject: [PATCH 119/184] Provide proper support for R2/R3 choice through command line options in utility --- main/main.cpp | 198 +++++++++++++++++++++++++++++++++----------------- meson.build | 31 ++++++-- 2 files changed, 158 insertions(+), 71 deletions(-) diff --git a/main/main.cpp b/main/main.cpp index 16e559d..5f6992a 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -103,7 +103,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; @@ -128,11 +131,17 @@ 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"); + 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' }, @@ -148,10 +157,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' }, @@ -163,16 +172,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:", + "t:p:d:RLPFc:f:T:D:qhHVM:23", longOpts, &optionIndex); if (c == -1) break; switch (c) { 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; @@ -187,9 +199,9 @@ int main(int argc, char **argv) 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; @@ -202,6 +214,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; } } @@ -220,15 +234,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; @@ -239,7 +253,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; @@ -267,55 +281,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 << " -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; + 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; } @@ -323,6 +368,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; @@ -352,17 +414,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; @@ -502,10 +569,9 @@ int main(int argc, char **argv) } RubberBandStretcher::Options options = 0; - - //!!! - cerr << "\n\n\n\n\n*** WARNING - THIS IS A TEST VERSION ONLY\n\n\n\n\n" << endl; - options = RubberBandStretcher::OptionEngineFiner; + if (finer) { + options = RubberBandStretcher::OptionEngineFiner; + } if (realtime) options |= RubberBandStretcher::OptionProcessRealTime; if (!lamination) options |= RubberBandStretcher::OptionPhaseIndependent; diff --git a/meson.build b/meson.build index 5dd0492..282cf8f 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,7 @@ project( 'Rubber Band Library', 'c', 'cpp', - version: '3.0.0', + version: '3.0.0-pre', license: 'GPL-2.0-or-later', default_options: [ 'cpp_std=c++11', @@ -448,6 +448,7 @@ 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' @@ -457,6 +458,7 @@ 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' @@ -686,8 +688,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, @@ -705,9 +707,28 @@ 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 From 8b25206e7fa17da86fe63957e5dfd084b03a31a2 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 15 Jun 2022 11:58:46 +0100 Subject: [PATCH 120/184] Fix some compiler warnings --- src/finer/Guide.h | 5 ++--- src/test/TestBinClassifier.cpp | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/finer/Guide.h b/src/finer/Guide.h index 74058e6..1f90d69 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -168,9 +168,8 @@ public: guidance.fftBands[1].f1 = m_minHigher; guidance.fftBands[2].f0 = m_minHigher; guidance.fftBands[2].f1 = nyquist; - for (int i = 0; i < - sizeof(guidance.phaseLockBands) / - sizeof(guidance.phaseLockBands[0]); ++i) { + for (int i = 0; i < int(sizeof(guidance.phaseLockBands) / + sizeof(guidance.phaseLockBands[0])); ++i) { guidance.phaseLockBands[i].p = 0; guidance.phaseLockBands[i].beta = 1.0; guidance.phaseLockBands[i].f0 = nyquist; diff --git a/src/test/TestBinClassifier.cpp b/src/test/TestBinClassifier.cpp index e266287..f1786ab 100644 --- a/src/test/TestBinClassifier.cpp +++ b/src/test/TestBinClassifier.cpp @@ -43,7 +43,7 @@ static constexpr auto _ = BinClassifier::Classification::Residual; vector classes_to_strings(const vector &v) { vector sv(v.size(), "*"); - for (auto i = 0; i < v.size(); ++i) { + for (auto i = 0; i < int(v.size()); ++i) { switch (v[i]) { case H: sv[i] = "H"; break; case X: sv[i] = "X"; break; From 321544e05d7cf5edbc7bb89bb429961a6b881325 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 15 Jun 2022 14:28:40 +0100 Subject: [PATCH 121/184] Update version for beta1 --- meson.build | 2 +- otherbuilds/deploy/source.sh | 11 ++++++++++- rubberband/RubberBandStretcher.h | 2 +- rubberband/rubberband-c.h | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/meson.build b/meson.build index 282cf8f..0e8d6cd 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,7 @@ project( 'Rubber Band Library', 'c', 'cpp', - version: '3.0.0-pre', + version: '3.0.0-beta1', license: 'GPL-2.0-or-later', default_options: [ 'cpp_std=c++11', 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/rubberband/RubberBandStretcher.h b/rubberband/RubberBandStretcher.h index ef81286..a79d6f4 100644 --- a/rubberband/RubberBandStretcher.h +++ b/rubberband/RubberBandStretcher.h @@ -24,7 +24,7 @@ #ifndef RUBBERBAND_STRETCHER_H #define RUBBERBAND_STRETCHER_H -#define RUBBERBAND_VERSION "3.0.0" +#define RUBBERBAND_VERSION "3.0.0-beta1" #define RUBBERBAND_API_MAJOR_VERSION 2 #define RUBBERBAND_API_MINOR_VERSION 7 diff --git a/rubberband/rubberband-c.h b/rubberband/rubberband-c.h index 521a531..174d8a2 100644 --- a/rubberband/rubberband-c.h +++ b/rubberband/rubberband-c.h @@ -28,7 +28,7 @@ extern "C" { #endif -#define RUBBERBAND_VERSION "3.0.0" +#define RUBBERBAND_VERSION "3.0.0-beta1" #define RUBBERBAND_API_MAJOR_VERSION 2 #define RUBBERBAND_API_MINOR_VERSION 7 From 3865c780c0fbfba35d7654c8397bf1342ba0d0ec Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 15 Jun 2022 17:22:12 +0100 Subject: [PATCH 122/184] Added tag v3.0.0-beta1 for changeset 590cb5c496f8 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 8711a89..7466d6c 100644 --- a/.hgtags +++ b/.hgtags @@ -16,3 +16,4 @@ fa6a54be7e6bf0c5adffd19ccec622481a8140a5 v1.8.2 4a6f7059b6b77fb34a9f29037f3ece47755e99a0 v2.0.0 190ba65557c06823ef576d5d62b99e2d27771759 v2.0.1 4e2177c66756fecacccf211df5f8a97d01070ef0 v2.0.2 +590cb5c496f868d9806db5205bfe22ddeb7f5767 v3.0.0-beta1 From 326f9b721d74834b654fcc295b8ef2e773e763d2 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 15 Jun 2022 17:22:32 +0100 Subject: [PATCH 123/184] Add RTENTRY macro for STOAT testing --- src/common/sysutils.h | 6 ++++++ src/finer/R3StretcherImpl.cpp | 8 +++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/common/sysutils.h b/src/common/sysutils.h index 06755ec..e0532f3 100644 --- a/src/common/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 diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 5f9ffeb..e471372 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -384,12 +384,10 @@ R3StretcherImpl::getSamplesRequired() const } } -//!!! __attribute__((annotate("realtime"))) +RTENTRY__ void R3StretcherImpl::process(const float *const *input, size_t samples, bool final) { - //!!! todo: final - if (m_mode == ProcessMode::Finished) { m_parameters.logger("R3StretcherImpl::process: Cannot process again after final chunk"); return; @@ -432,7 +430,7 @@ R3StretcherImpl::process(const float *const *input, size_t samples, bool final) consume(); } -//!!! __attribute__((annotate("realtime"))) +RTENTRY__ int R3StretcherImpl::available() const { @@ -444,7 +442,7 @@ R3StretcherImpl::available() const } } -//!!! __attribute__((annotate("realtime"))) +RTENTRY__ size_t R3StretcherImpl::retrieve(float *const *output, size_t samples) const { From bcdd1149dfd545c6217ec3835823374c6787d60f Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 16 Jun 2022 08:55:21 +0100 Subject: [PATCH 124/184] Windows package updates --- main/main.cpp | 9 +++++++-- otherbuilds/deploy/win.bat | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/main/main.cpp b/main/main.cpp index 5f6992a..031474b 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -133,8 +133,13 @@ int main(int argc, char **argv) std::string myName(argv[0]); - bool isR3 = (myName.size() > 3 && - myName.substr(myName.size() - 3, 3) == "-r3"); + 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; 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 From 06521f00437c6ff7ddb879bc91d7cc5511f73307 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 16 Jun 2022 15:58:59 +0100 Subject: [PATCH 125/184] Avoid reusing variable name! --- main/main.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/main/main.cpp b/main/main.cpp index 031474b..d49e63c 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -84,8 +84,6 @@ 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; @@ -182,12 +180,12 @@ int main(int argc, char **argv) { 0, 0, 0, 0 } }; - c = getopt_long(argc, argv, - "t:p:d:RLPFc:f:T:D:qhHVM:23", - 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; From ea148cb3ca02692f98520ee8e601f12010cf4225 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 17 Jun 2022 08:26:05 +0100 Subject: [PATCH 126/184] More rt entry points --- src/RubberBandStretcher.cpp | 20 +++++++++++++++++--- src/finer/R3StretcherImpl.cpp | 3 --- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/RubberBandStretcher.cpp b/src/RubberBandStretcher.cpp index 8e62e33..f850d89 100644 --- a/src/RubberBandStretcher.cpp +++ b/src/RubberBandStretcher.cpp @@ -24,11 +24,8 @@ #include "faster/StretcherImpl.h" #include "finer/R3StretcherImpl.h" - namespace RubberBand { -//#define FASTER 1 - RubberBandStretcher::RubberBandStretcher(size_t sampleRate, size_t channels, Options options, @@ -61,6 +58,7 @@ RubberBandStretcher::reset() else m_r3d->reset(); } +RTENTRY__ void RubberBandStretcher::setTimeRatio(double ratio) { @@ -68,6 +66,7 @@ RubberBandStretcher::setTimeRatio(double ratio) else m_r3d->setTimeRatio(ratio); } +RTENTRY__ void RubberBandStretcher::setPitchScale(double scale) { @@ -75,12 +74,14 @@ RubberBandStretcher::setPitchScale(double scale) else m_r3d->setPitchScale(scale); } +RTENTRY__ void RubberBandStretcher::setFormantScale(double scale) { if (m_r3d) m_r3d->setFormantScale(scale); } +RTENTRY__ double RubberBandStretcher::getTimeRatio() const { @@ -88,6 +89,7 @@ RubberBandStretcher::getTimeRatio() const else return m_r3d->getTimeRatio(); } +RTENTRY__ double RubberBandStretcher::getPitchScale() const { @@ -95,6 +97,7 @@ RubberBandStretcher::getPitchScale() const else return m_r3d->getPitchScale(); } +RTENTRY__ double RubberBandStretcher::getFormantScale() const { @@ -102,6 +105,7 @@ RubberBandStretcher::getFormantScale() const else return m_r3d->getFormantScale(); } +RTENTRY__ size_t RubberBandStretcher::getLatency() const { @@ -111,24 +115,28 @@ RubberBandStretcher::getLatency() const //!!! review all these +RTENTRY__ void RubberBandStretcher::setTransientsOption(Options options) { if (m_d) m_d->setTransientsOption(options); } +RTENTRY__ void RubberBandStretcher::setDetectorOption(Options options) { if (m_d) m_d->setDetectorOption(options); } +RTENTRY__ void RubberBandStretcher::setPhaseOption(Options options) { if (m_d) m_d->setPhaseOption(options); } +RTENTRY__ void RubberBandStretcher::setFormantOption(Options options) { @@ -136,6 +144,7 @@ RubberBandStretcher::setFormantOption(Options options) else if (m_r3d) m_r3d->setFormantOption(options); } +RTENTRY__ void RubberBandStretcher::setPitchOption(Options options) { @@ -161,6 +170,7 @@ RubberBandStretcher::setKeyFrameMap(const std::map &mapping) else m_r3d->setKeyFrameMap(mapping); } +RTENTRY__ size_t RubberBandStretcher::getSamplesRequired() const { @@ -176,6 +186,7 @@ RubberBandStretcher::study(const float *const *input, size_t samples, else m_r3d->study(input, samples, final); } +RTENTRY__ void RubberBandStretcher::process(const float *const *input, size_t samples, bool final) @@ -184,6 +195,7 @@ RubberBandStretcher::process(const float *const *input, size_t samples, else m_r3d->process(input, samples, final); } +RTENTRY__ int RubberBandStretcher::available() const { @@ -191,6 +203,7 @@ RubberBandStretcher::available() const else return m_r3d->available(); } +RTENTRY__ size_t RubberBandStretcher::retrieve(float *const *output, size_t samples) const { @@ -239,6 +252,7 @@ RubberBandStretcher::getExactTimePoints() const else return {}; } +RTENTRY__ size_t RubberBandStretcher::getChannelCount() const { diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index e471372..e9dbf88 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -384,7 +384,6 @@ R3StretcherImpl::getSamplesRequired() const } } -RTENTRY__ void R3StretcherImpl::process(const float *const *input, size_t samples, bool final) { @@ -430,7 +429,6 @@ R3StretcherImpl::process(const float *const *input, size_t samples, bool final) consume(); } -RTENTRY__ int R3StretcherImpl::available() const { @@ -442,7 +440,6 @@ R3StretcherImpl::available() const } } -RTENTRY__ size_t R3StretcherImpl::retrieve(float *const *output, size_t samples) const { From 380cad53b46f916cd9a52e34873ca640851035b9 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 17 Jun 2022 15:01:26 +0100 Subject: [PATCH 127/184] Attempt to "fade in" phase resets on return to unity, so as to return to an approximate pass-through without too audible a click --- src/finer/Guide.h | 104 +++++++++++++++++++++++++++------- src/finer/PhaseAdvance.h | 13 +++-- src/finer/R3StretcherImpl.cpp | 16 +++++- src/finer/R3StretcherImpl.h | 1 + 4 files changed, 104 insertions(+), 30 deletions(-) diff --git a/src/finer/Guide.h b/src/finer/Guide.h index 1f90d69..2e07c4e 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -147,37 +147,37 @@ public: const BinSegmenter::Segmentation &segmentation, const BinSegmenter::Segmentation &prevSegmentation, const BinSegmenter::Segmentation &nextSegmentation, - bool specialCaseUnity, + double meanMagnitude, + int unityCount, 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.phaseReset.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))); - if (specialCaseUnity && (fabs(ratio - 1.0) < 1.0e-6)) { - guidance.fftBands[0].f0 = 0.0; - guidance.fftBands[0].f1 = 0.0; - guidance.fftBands[1].f0 = 0.0; - guidance.fftBands[1].f1 = m_minHigher; - guidance.fftBands[2].f0 = m_minHigher; - guidance.fftBands[2].f1 = nyquist; - for (int i = 0; i < int(sizeof(guidance.phaseLockBands) / - sizeof(guidance.phaseLockBands[0])); ++i) { - guidance.phaseLockBands[i].p = 0; - guidance.phaseLockBands[i].beta = 1.0; - guidance.phaseLockBands[i].f0 = nyquist; - guidance.phaseLockBands[i].f1 = nyquist; - } - guidance.phaseLockBands[0].f0 = 0.0; - guidance.phaseLockBands[0].f1 = nyquist; - guidance.channelLock.present = false; + // 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); return; } @@ -351,7 +351,69 @@ protected: 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) const { + +// std::cout << "unity" << std::endl; + + double nyquist = m_parameters.sampleRate / 2.0; + + 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, diff --git a/src/finer/PhaseAdvance.h b/src/finer/PhaseAdvance.h index 0808699..5fc6f99 100644 --- a/src/finer/PhaseAdvance.h +++ b/src/finer/PhaseAdvance.h @@ -186,19 +186,20 @@ public: ++phaseLockBand; } double ph = 0.0; - if (inhop == outhop) { - ph = m_unlocked[c][i]; - } else if (inRange(f, g->phaseReset) || inRange(f, g->kick)) { + 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)) { + if (inRange(f, g->channelLock)) { int other = m_greatestChannel[i]; - if (other != c) { + if (other != c && + inRange(f, guidance[other]->channelLock)) { int otherPeak = m_currentPeaks[other][i]; int otherPrevPeak = m_prevPeaks[other][otherPeak]; if (otherPrevPeak == prevPeak) { @@ -216,7 +217,7 @@ public: double(g->phaseLockBands[phaseLockBand].beta); ph = peakNew + beta * diff; } - outPhase[c][i] = ph; + outPhase[c][i] = princarg(ph); } } diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index e471372..f97c727 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -40,7 +40,9 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, 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), @@ -826,9 +828,16 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) std::cout << std::endl; } */ - bool specialCaseUnity = true; + + double ratio = getEffectiveRatio(); + + if (fabs(ratio - 1.0) < 1.0e-6) { + ++m_unityCount; + } else { + m_unityCount = 0; + } - m_guide.updateGuidance(getEffectiveRatio(), + m_guide.updateGuidance(ratio, prevOuthop, classifyScale->mag.data(), classifyScale->prevMag.data(), @@ -836,7 +845,8 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) cd->segmentation, cd->prevSegmentation, cd->nextSegmentation, - specialCaseUnity, + v_mean(classifyScale->mag.data() + 1, classify/2), + m_unityCount, cd->guidance); /* if (c == 0) { diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 198e03a..aa08d27 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -281,6 +281,7 @@ protected: std::atomic m_inhop; int m_prevInhop; int m_prevOuthop; + uint32_t m_unityCount; int m_startSkip; size_t m_studyInputDuration; From c8171e7f9d03da23febea8df95d073677171aa89 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 17 Jun 2022 15:19:52 +0100 Subject: [PATCH 128/184] Added tag v3.0.0-beta2 for changeset acc04c20175e --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 7466d6c..e972cf1 100644 --- a/.hgtags +++ b/.hgtags @@ -17,3 +17,4 @@ fa6a54be7e6bf0c5adffd19ccec622481a8140a5 v1.8.2 190ba65557c06823ef576d5d62b99e2d27771759 v2.0.1 4e2177c66756fecacccf211df5f8a97d01070ef0 v2.0.2 590cb5c496f868d9806db5205bfe22ddeb7f5767 v3.0.0-beta1 +acc04c20175ebc3f8138c4e14c9108cb33024fb1 v3.0.0-beta2 From 750dd650cf48c9a54c67760baefd34fc4de87704 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 17 Jun 2022 15:20:39 +0100 Subject: [PATCH 129/184] Beta2 --- meson.build | 2 +- rubberband/RubberBandStretcher.h | 2 +- rubberband/rubberband-c.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/meson.build b/meson.build index 0e8d6cd..a9404e9 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,7 @@ project( 'Rubber Band Library', 'c', 'cpp', - version: '3.0.0-beta1', + version: '3.0.0-beta2', license: 'GPL-2.0-or-later', default_options: [ 'cpp_std=c++11', diff --git a/rubberband/RubberBandStretcher.h b/rubberband/RubberBandStretcher.h index a79d6f4..b14a440 100644 --- a/rubberband/RubberBandStretcher.h +++ b/rubberband/RubberBandStretcher.h @@ -24,7 +24,7 @@ #ifndef RUBBERBAND_STRETCHER_H #define RUBBERBAND_STRETCHER_H -#define RUBBERBAND_VERSION "3.0.0-beta1" +#define RUBBERBAND_VERSION "3.0.0-beta2" #define RUBBERBAND_API_MAJOR_VERSION 2 #define RUBBERBAND_API_MINOR_VERSION 7 diff --git a/rubberband/rubberband-c.h b/rubberband/rubberband-c.h index 174d8a2..231f99a 100644 --- a/rubberband/rubberband-c.h +++ b/rubberband/rubberband-c.h @@ -28,7 +28,7 @@ extern "C" { #endif -#define RUBBERBAND_VERSION "3.0.0-beta1" +#define RUBBERBAND_VERSION "3.0.0-beta2" #define RUBBERBAND_API_MAJOR_VERSION 2 #define RUBBERBAND_API_MINOR_VERSION 7 From 08f7fce5f27a061d3111c9e92e4a1bd1bee00f51 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 17 Jun 2022 15:20:44 +0100 Subject: [PATCH 130/184] Added tag v3.0.0-beta2 for changeset ed9acf241b10 --- .hgtags | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.hgtags b/.hgtags index e972cf1..cd4ed79 100644 --- a/.hgtags +++ b/.hgtags @@ -18,3 +18,5 @@ fa6a54be7e6bf0c5adffd19ccec622481a8140a5 v1.8.2 4e2177c66756fecacccf211df5f8a97d01070ef0 v2.0.2 590cb5c496f868d9806db5205bfe22ddeb7f5767 v3.0.0-beta1 acc04c20175ebc3f8138c4e14c9108cb33024fb1 v3.0.0-beta2 +acc04c20175ebc3f8138c4e14c9108cb33024fb1 v3.0.0-beta2 +ed9acf241b1076e84ba0b41dc9d8edd904f69f25 v3.0.0-beta2 From 6128ba6d36418a35bcd79e1aac9bdf9daf611429 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 17 Jun 2022 16:32:14 +0100 Subject: [PATCH 131/184] Add convertToPolar to capture some of the awkward decisions in analyseChannel --- src/finer/R3StretcherImpl.cpp | 90 ++++++++++++++++------------------- src/finer/R3StretcherImpl.h | 31 ++++++++++++ 2 files changed, 72 insertions(+), 49 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index e41cf31..cf0d4b3 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -700,26 +700,17 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) for (const auto &b : m_guideConfiguration.fftBandLimits) { if (b.fftSize == classify) { - if (b.b0min > 0) { - v_cartesian_to_magnitudes(readahead.mag.data(), - classifyScale->real.data(), - classifyScale->imag.data(), - b.b0min); - } - - v_cartesian_to_polar(readahead.mag.data() + b.b0min, - readahead.phase.data() + b.b0min, - classifyScale->real.data() + b.b0min, - classifyScale->imag.data() + b.b0min, - b.b1max - b.b0min); - - if (b.b1max < classify/2 + 1) { - v_cartesian_to_magnitudes - (readahead.mag.data() + b.b1max, - classifyScale->real.data() + b.b1max, - classifyScale->imag.data() + b.b1max, - classify/2 + 1 - b.b1max); - } + + 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), @@ -749,38 +740,39 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) scale->real.data(), scale->imag.data()); - // For the classify scale we always want the full range, as - // all the magnitudes (though not 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) { - //!!! and because not all the phases are relevant, there - //!!! is room for an optimisation here, though this is - //!!! used only when ratio changes - v_cartesian_to_polar(scale->mag.data(), - scale->phase.data(), - scale->real.data(), - scale->imag.data(), - fftSize/2 + 1); - v_scale(scale->mag.data(), - 1.0 / double(fftSize), - scale->mag.size()); - continue; - } - - //!!! should this be a map? for (const auto &b : m_guideConfiguration.fftBandLimits) { if (b.fftSize == fftSize) { - v_cartesian_to_polar(scale->mag.data() + b.b0min, - scale->phase.data() + b.b0min, - scale->real.data() + b.b0min, - scale->imag.data() + b.b0min, - b.b1max - b.b0min); - v_scale(scale->mag.data() + b.b0min, + + ToPolarSpec spec; + + // For the classify scale we always want the full + // range, as all the magnitudes (though not 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), - b.b1max - b.b0min); + spec.magBinCount); + break; } } diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index aa08d27..40e3983 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -35,6 +35,7 @@ #include "../common/FixedVector.h" #include "../common/Allocators.h" #include "../common/Window.h" +#include "../common/VectorOpsComplex.h" #include "../../rubberband/RubberBandStretcher.h" @@ -307,6 +308,36 @@ protected: 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; } From b4e921003f02c66088120fca699c3c1ca82934aa Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 17 Jun 2022 16:52:55 +0100 Subject: [PATCH 132/184] Perform polar-to-cartesian conversions only on range that will actually be used --- src/finer/R3StretcherImpl.cpp | 51 +++++++++++++---------------------- 1 file changed, 18 insertions(+), 33 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index cf0d4b3..3a830cf 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -976,45 +976,30 @@ R3StretcherImpl::synthesiseChannel(int c, int outhop) // The frequency filter is applied naively in the frequency // domain. Aliasing is reduced by the shorter resynthesis - // window - + // 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; - for (int i = 0; i < lowBin; ++i) { - scale->mag[i] = 0.0; + if (lowBin > 0) { + v_zero(scale->real.data(), lowBin); + v_zero(scale->imag.data(), lowBin); } - for (int i = lowBin; i < highBin; ++i) { - scale->mag[i] *= winscale; - } - for (int i = highBin; i < fftSize/2 + 1; ++i) { - scale->mag[i] = 0.0; - } - } - // Resynthesise each FFT size (scale) individually, then sum. This - // is easier to manage scaling for in situations with a varying - // resynthesis hop - - for (auto &it : cd->scales) { - int fftSize = it.first; - auto &scale = it.second; - auto &scaleData = m_scaleData.at(fftSize); - - for (const auto &b : m_guideConfiguration.fftBandLimits) { - if (b.fftSize == fftSize) { - int offset = b.b0min; - v_zero(scale->real.data(), fftSize/2 + 1); - v_zero(scale->imag.data(), fftSize/2 + 1); - v_polar_to_cartesian - (scale->real.data() + offset, - scale->imag.data() + offset, - scale->mag.data() + offset, - scale->advancedPhase.data() + offset, - b.b1max - offset); - break; - } + 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 < fftSize/2 + 1) { + v_zero(scale->real.data() + highBin, fftSize/2 + 1 - highBin); + v_zero(scale->imag.data() + highBin, fftSize/2 + 1 - highBin); } scaleData->fft.inverse(scale->real.data(), From 8c07d06d72d312f4f312379ce9f5f81cdd75cd32 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 17 Jun 2022 16:56:09 +0100 Subject: [PATCH 133/184] Small simplification --- src/finer/R3StretcherImpl.cpp | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 3a830cf..46593c4 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -746,11 +746,12 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) ToPolarSpec spec; // For the classify scale we always want the full - // range, as all the magnitudes (though not 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. + // 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; @@ -955,23 +956,17 @@ R3StretcherImpl::synthesiseChannel(int c, int outhop) int longest = m_guideConfiguration.longestFftSize; auto &cd = m_channelData.at(c); - - for (auto &it : cd->scales) { - - auto &scale = it.second; - int bufSize = scale->bufSize; - - // copy to prevMag before filtering - v_copy(scale->prevMag.data(), - scale->mag.data(), - bufSize); - } 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 @@ -997,9 +992,9 @@ R3StretcherImpl::synthesiseChannel(int c, int outhop) scale->advancedPhase.data() + lowBin, highBin - lowBin); - if (highBin < fftSize/2 + 1) { - v_zero(scale->real.data() + highBin, fftSize/2 + 1 - highBin); - v_zero(scale->imag.data() + highBin, fftSize/2 + 1 - highBin); + 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(), From f3f06c55cbe852c445a223d1e04d72c504f43f09 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 17 Jun 2022 17:52:28 +0100 Subject: [PATCH 134/184] Sketch implementation of setKeyFrameMap --- src/finer/R3StretcherImpl.cpp | 60 +++++++++++++++++++++++++++++++++-- src/finer/R3StretcherImpl.h | 1 + 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3StretcherImpl.cpp index 46593c4..e349243 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3StretcherImpl.cpp @@ -47,6 +47,7 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, m_studyInputDuration(0), m_totalTargetDuration(0), m_processInputDuration(0), + m_lastKeyFrameSurpassed(0), m_totalOutputDuration(0), m_mode(ProcessMode::JustCreated) { @@ -287,8 +288,59 @@ void R3StretcherImpl::updateRatioFromMap() { if (m_keyFrameMap.empty()) return; -//!!! auto itr = m_keyFrameMap.upper_bound(m_processInputDuration); - + + if (m_processInputDuration == 0) { + m_timeRatio = double(m_keyFrameMap.begin()->second) / + double(m_keyFrameMap.begin()->first); + std::cout << "initial key-frame map entry " << m_keyFrameMap.begin()->first << " -> " << m_keyFrameMap.begin()->second << " gives initial ratio " << m_timeRatio << std::endl; + calculateHop(); + m_lastKeyFrameSurpassed = 0; + return; + } + + auto i0 = m_keyFrameMap.upper_bound(m_lastKeyFrameSurpassed); + + if (i0 == m_keyFrameMap.end()) { + return; + } + + if (m_processInputDuration >= i0->first) { + + std::cout << "at " << m_processInputDuration << " (output = " << m_totalOutputDuration << ") we have passed " << i0->first << ", looking ahead to next key frame" << std::endl; + + 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; + size_t toKeyFrameAtInput = keyFrameAtInput - m_processInputDuration; + size_t toKeyFrameAtOutput = 0; + + if (keyFrameAtOutput > m_totalOutputDuration) { + toKeyFrameAtOutput = keyFrameAtOutput - m_totalOutputDuration; + } + + double ratio = double(toKeyFrameAtOutput) / double(toKeyFrameAtInput); + + std::cout << "keyFrameAtInput = " << keyFrameAtInput << ", keyFrameAtOutput = " << keyFrameAtOutput << std::endl; + std::cout << "currently at input = " << m_processInputDuration << ", currently at output = " << m_totalOutputDuration << std::endl; + std::cout << "toKeyFrameAtInput = " << toKeyFrameAtInput << ", toKeyFrameAtOutput = " << toKeyFrameAtOutput << std::endl; + std::cout << "ratio = " << ratio << std::endl; + + m_timeRatio = ratio; + calculateHop(); + + m_lastKeyFrameSurpassed = i0->first; + } } double @@ -346,6 +398,7 @@ R3StretcherImpl::reset() m_studyInputDuration = 0; m_totalTargetDuration = 0; m_processInputDuration = 0; + m_lastKeyFrameSurpassed = 0; m_totalOutputDuration = 0; m_keyFrameMap.clear(); @@ -399,6 +452,7 @@ R3StretcherImpl::process(const float *const *input, size_t samples, bool final) m_totalTargetDuration = size_t(round(m_studyInputDuration * getEffectiveRatio())); } + updateRatioFromMap(); } if (final) { @@ -589,8 +643,10 @@ R3StretcherImpl::consume() auto &cd = m_channelData.at(c); if (m_resampler) { cd->outbuf->write(cd->resampled.data(), resampledCount); + if (c == 0) m_totalOutputDuration += resampledCount; } else { cd->outbuf->write(cd->mixdown.data(), outhop); + if (c == 0) m_totalOutputDuration += outhop; } int readSpace = cd->inbuf->getReadSpace(); diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3StretcherImpl.h index 40e3983..bc23d70 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3StretcherImpl.h @@ -288,6 +288,7 @@ protected: size_t m_studyInputDuration; size_t m_totalTargetDuration; size_t m_processInputDuration; + size_t m_lastKeyFrameSurpassed; size_t m_totalOutputDuration; std::map m_keyFrameMap; From e546767a6d993fab0fbc9601ee4154a6d4427161 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 21 Jun 2022 10:25:08 +0100 Subject: [PATCH 135/184] Rename/rejig so as to have just a single Impl and then R2 and R3 stretcher classes --- README.md | 2 +- meson.build | 4 +- rubberband/RubberBandStretcher.h | 5 +- single/RubberBandSingle.cpp | 2 +- src/RubberBandStretcher.cpp | 356 ++++++++++++++---- src/common/sysutils.h | 6 + .../{StretcherImpl.cpp => R2Stretcher.cpp} | 165 ++++---- src/faster/{StretcherImpl.h => R2Stretcher.h} | 31 +- src/faster/StretcherChannelData.cpp | 32 +- src/faster/StretcherChannelData.h | 4 +- src/faster/StretcherProcess.cpp | 120 +++--- src/finer/Guide.h | 5 +- .../{R3StretcherImpl.cpp => R3Stretcher.cpp} | 86 ++--- .../{R3StretcherImpl.h => R3Stretcher.h} | 10 +- 14 files changed, 534 insertions(+), 294 deletions(-) rename src/faster/{StretcherImpl.cpp => R2Stretcher.cpp} (87%) rename src/faster/{StretcherImpl.h => R2Stretcher.h} (92%) rename src/finer/{R3StretcherImpl.cpp => R3Stretcher.cpp} (92%) rename src/finer/{R3StretcherImpl.h => R3Stretcher.h} (98%) diff --git a/README.md b/README.md index ffe204c..16671ff 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,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 ``` diff --git a/meson.build b/meson.build index a9404e9..442e274 100644 --- a/meson.build +++ b/meson.build @@ -39,8 +39,8 @@ library_sources = [ 'src/faster/HighFrequencyAudioCurve.cpp', 'src/faster/SilentAudioCurve.cpp', 'src/faster/PercussiveAudioCurve.cpp', + 'src/faster/R2Stretcher.cpp', 'src/faster/StretcherChannelData.cpp', - 'src/faster/StretcherImpl.cpp', 'src/faster/StretcherProcess.cpp', 'src/common/Profiler.cpp', 'src/common/Resampler.cpp', @@ -49,7 +49,7 @@ library_sources = [ 'src/common/StretchCalculator.cpp', 'src/common/sysutils.cpp', 'src/common/Thread.cpp', - 'src/finer/R3StretcherImpl.cpp', + 'src/finer/R3Stretcher.cpp', ] jni_sources = [ diff --git a/rubberband/RubberBandStretcher.h b/rubberband/RubberBandStretcher.h index b14a440..60fc9d9 100644 --- a/rubberband/RubberBandStretcher.h +++ b/rubberband/RubberBandStretcher.h @@ -81,7 +81,6 @@ namespace RubberBand { -class R3StretcherImpl;//!!! class RUBBERBAND_DLLEXPORT RubberBandStretcher @@ -807,7 +806,9 @@ public: protected: class Impl; Impl *m_d; - R3StretcherImpl *m_r3d; + + RubberBandStretcher(const RubberBandStretcher &) =delete; + RubberBandStretcher &operator=(const RubberBandStretcher &) =delete; }; } diff --git a/single/RubberBandSingle.cpp b/single/RubberBandSingle.cpp index a04b2c4..bea6a76 100644 --- a/single/RubberBandSingle.cpp +++ b/single/RubberBandSingle.cpp @@ -70,7 +70,7 @@ #include "../src/common/sysutils.cpp" #include "../src/common/Thread.cpp" #include "../src/faster/StretcherChannelData.cpp" -#include "../src/faster/StretcherImpl.cpp" +#include "../src/faster/R2StretcherImpl.cpp" #include "../src/faster/StretcherProcess.cpp" #include "../src/finer/R3StretcherImpl.cpp" diff --git a/src/RubberBandStretcher.cpp b/src/RubberBandStretcher.cpp index f850d89..6043217 100644 --- a/src/RubberBandStretcher.cpp +++ b/src/RubberBandStretcher.cpp @@ -21,169 +21,402 @@ you must obtain a valid commercial licence before doing so. */ -#include "faster/StretcherImpl.h" -#include "finer/R3StretcherImpl.h" +#include "faster/R2Stretcher.h" +#include "finer/R3Stretcher.h" namespace RubberBand { +class RubberBandStretcher::Impl +{ + R2Stretcher *m_r2; + R3Stretcher *m_r3; + +public: + Impl(size_t sampleRate, size_t channels, Options options, + double initialTimeRatio, double initialPitchScale) : + m_r2 (!(options & OptionEngineFiner) ? + new R2Stretcher(sampleRate, channels, options, + initialTimeRatio, initialPitchScale) + : nullptr), + m_r3 ((options & OptionEngineFiner) ? + new R3Stretcher(R3Stretcher::Parameters + (double(sampleRate), channels, options), + initialTimeRatio, initialPitchScale) + : nullptr) + { + } + + ~Impl() + { + delete m_r2; + delete m_r3; + } + + 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); + } + + void + setMaxProcessSize(size_t samples) + { + if (m_r2) m_r2->setMaxProcessSize(samples); //!!! definitely need for r3d + } + + 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); + } + + static void + setDefaultDebugLevel(int level) + { + R2Stretcher::setDefaultDebugLevel(level); +//!!! R3Stretcher::setDefaultDebugLevel(level); + } +}; + RubberBandStretcher::RubberBandStretcher(size_t sampleRate, size_t channels, Options options, double initialTimeRatio, double initialPitchScale) : - m_d - (!(options & OptionEngineFiner) ? - new Impl(sampleRate, channels, options, - initialTimeRatio, initialPitchScale) - : nullptr), - m_r3d - ((options & OptionEngineFiner) ? - new R3StretcherImpl(R3StretcherImpl::Parameters - (double(sampleRate), channels, options), - initialTimeRatio, initialPitchScale) - : nullptr) + m_d(new Impl(sampleRate, channels, options, + initialTimeRatio, initialPitchScale)) { } RubberBandStretcher::~RubberBandStretcher() { delete m_d; - delete m_r3d; } void RubberBandStretcher::reset() { - if (m_d) m_d->reset(); - else m_r3d->reset(); + m_d->reset(); } RTENTRY__ void RubberBandStretcher::setTimeRatio(double ratio) { - if (m_d) m_d->setTimeRatio(ratio); - else m_r3d->setTimeRatio(ratio); + m_d->setTimeRatio(ratio); } RTENTRY__ void RubberBandStretcher::setPitchScale(double scale) { - if (m_d) m_d->setPitchScale(scale); - else m_r3d->setPitchScale(scale); + m_d->setPitchScale(scale); } RTENTRY__ void RubberBandStretcher::setFormantScale(double scale) { - if (m_r3d) m_r3d->setFormantScale(scale); + m_d->setFormantScale(scale); } RTENTRY__ double RubberBandStretcher::getTimeRatio() const { - if (m_d) return m_d->getTimeRatio(); - else return m_r3d->getTimeRatio(); + return m_d->getTimeRatio(); } RTENTRY__ double RubberBandStretcher::getPitchScale() const { - if (m_d) return m_d->getPitchScale(); - else return m_r3d->getPitchScale(); + return m_d->getPitchScale(); } RTENTRY__ double RubberBandStretcher::getFormantScale() const { - if (m_d) return 0.0; - else return m_r3d->getFormantScale(); + return m_d->getFormantScale(); } RTENTRY__ size_t RubberBandStretcher::getLatency() const { - if (m_d) return m_d->getLatency(); - else return m_r3d->getLatency(); + return m_d->getLatency(); } -//!!! review all these - RTENTRY__ void RubberBandStretcher::setTransientsOption(Options options) { - if (m_d) m_d->setTransientsOption(options); + m_d->setTransientsOption(options); } RTENTRY__ void RubberBandStretcher::setDetectorOption(Options options) { - if (m_d) m_d->setDetectorOption(options); + m_d->setDetectorOption(options); } RTENTRY__ void RubberBandStretcher::setPhaseOption(Options options) { - if (m_d) m_d->setPhaseOption(options); + m_d->setPhaseOption(options); } RTENTRY__ void RubberBandStretcher::setFormantOption(Options options) { - if (m_d) m_d->setFormantOption(options); - else if (m_r3d) m_r3d->setFormantOption(options); + m_d->setFormantOption(options); } RTENTRY__ void RubberBandStretcher::setPitchOption(Options options) { - if (m_d) m_d->setPitchOption(options); + m_d->setPitchOption(options); } void RubberBandStretcher::setExpectedInputDuration(size_t samples) { - if (m_d) m_d->setExpectedInputDuration(samples); + m_d->setExpectedInputDuration(samples); } void RubberBandStretcher::setMaxProcessSize(size_t samples) { - if (m_d) m_d->setMaxProcessSize(samples); //!!! definitely need for r3d + m_d->setMaxProcessSize(samples); } void RubberBandStretcher::setKeyFrameMap(const std::map &mapping) { - if (m_d) m_d->setKeyFrameMap(mapping); - else m_r3d->setKeyFrameMap(mapping); + m_d->setKeyFrameMap(mapping); } RTENTRY__ size_t RubberBandStretcher::getSamplesRequired() const { - if (m_d) return m_d->getSamplesRequired(); - else return m_r3d->getSamplesRequired(); + return m_d->getSamplesRequired(); } void RubberBandStretcher::study(const float *const *input, size_t samples, bool final) { - if (m_d) m_d->study(input, samples, final); - else m_r3d->study(input, samples, final); + m_d->study(input, samples, final); } RTENTRY__ @@ -191,85 +424,76 @@ void RubberBandStretcher::process(const float *const *input, size_t samples, bool final) { - if (m_d) m_d->process(input, samples, final); - else m_r3d->process(input, samples, final); + m_d->process(input, samples, final); } RTENTRY__ int RubberBandStretcher::available() const { - if (m_d) return m_d->available(); - else return m_r3d->available(); + return m_d->available(); } RTENTRY__ size_t RubberBandStretcher::retrieve(float *const *output, size_t samples) const { - if (m_d) return m_d->retrieve(output, samples); - else return m_r3d->retrieve(output, samples); + return m_d->retrieve(output, samples); } float RubberBandStretcher::getFrequencyCutoff(int n) const { - if (m_d) return m_d->getFrequencyCutoff(n); - else return {}; + return m_d->getFrequencyCutoff(n); } void RubberBandStretcher::setFrequencyCutoff(int n, float f) { - if (m_d) m_d->setFrequencyCutoff(n, f); + m_d->setFrequencyCutoff(n, f); } size_t RubberBandStretcher::getInputIncrement() const { - if (m_d) return m_d->getInputIncrement(); - else return {}; + return m_d->getInputIncrement(); } std::vector RubberBandStretcher::getOutputIncrements() const { - if (m_d) return m_d->getOutputIncrements(); - else return {}; + return m_d->getOutputIncrements(); } std::vector RubberBandStretcher::getPhaseResetCurve() const { - if (m_d) return m_d->getPhaseResetCurve(); - else return {}; + return m_d->getPhaseResetCurve(); } std::vector RubberBandStretcher::getExactTimePoints() const { - if (m_d) return m_d->getExactTimePoints(); - else return {}; + return m_d->getExactTimePoints(); } RTENTRY__ size_t RubberBandStretcher::getChannelCount() const { - if (m_d) return m_d->getChannelCount(); - else return m_r3d->getChannelCount(); + return m_d->getChannelCount(); } void RubberBandStretcher::calculateStretch() { - if (m_d) m_d->calculateStretch(); + m_d->calculateStretch(); } void RubberBandStretcher::setDebugLevel(int level) { - if (m_d) m_d->setDebugLevel(level); + m_d->setDebugLevel(level); } void diff --git a/src/common/sysutils.h b/src/common/sysutils.h index e0532f3..9bcdd9a 100644 --- a/src/common/sysutils.h +++ b/src/common/sysutils.h @@ -88,6 +88,12 @@ 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(); diff --git a/src/faster/StretcherImpl.cpp b/src/faster/R2Stretcher.cpp similarity index 87% rename from src/faster/StretcherImpl.cpp rename to src/faster/R2Stretcher.cpp index 4cc760b..202f69a 100644 --- a/src/faster/StretcherImpl.cpp +++ b/src/faster/R2Stretcher.cpp @@ -21,7 +21,7 @@ you must obtain a valid commercial licence before doing so. */ -#include "StretcherImpl.h" +#include "R2Stretcher.h" #include "PercussiveAudioCurve.h" #include "HighFrequencyAudioCurve.h" @@ -51,21 +51,21 @@ 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; +R2Stretcher::m_defaultFftSize = 2048; int -RubberBandStretcher::Impl::m_defaultDebugLevel = 0; +R2Stretcher::m_defaultDebugLevel = 0; 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) : m_sampleRate(sampleRate), m_channels(channels), m_timeRatio(initialTimeRatio), @@ -111,7 +111,7 @@ RubberBandStretcher::Impl::Impl(size_t sampleRate, } if (m_debugLevel > 0) { - cerr << "RubberBandStretcher::Impl::Impl: rate = " << m_sampleRate << ", options = " << options << endl; + cerr << "R2Stretcher::R2Stretcher: rate = " << m_sampleRate << ", options = " << options << endl; } // Window size will vary according to the audio sample rate, but @@ -120,15 +120,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)) { + cerr << "R2Stretcher::R2Stretcher: Cannot specify OptionWindowLong and OptionWindowShort together; falling back to OptionWindowStandard" << endl; + } 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) { + } else if (options & RubberBandStretcher::OptionWindowLong) { m_baseFftSize = m_baseFftSize * 2; if (m_debugLevel > 0) { cerr << "setting baseFftSize to " << m_baseFftSize << endl; @@ -141,7 +143,7 @@ RubberBandStretcher::Impl::Impl(size_t sampleRate, m_maxProcessSize = m_aWindowSize; } - if (m_options & OptionProcessRealTime) { + if (m_options & RubberBandStretcher::OptionProcessRealTime) { m_realtime = true; } @@ -152,9 +154,9 @@ 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; } @@ -168,7 +170,7 @@ RubberBandStretcher::Impl::Impl(size_t sampleRate, configure(); } -RubberBandStretcher::Impl::~Impl() +R2Stretcher::~R2Stretcher() { #ifndef NO_THREADING if (m_threaded) { @@ -205,7 +207,7 @@ RubberBandStretcher::Impl::~Impl() } void -RubberBandStretcher::Impl::reset() +R2Stretcher::reset() { #ifndef NO_THREADING if (m_threaded) { @@ -247,11 +249,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; + cerr << "R2Stretcher::setTimeRatio: Cannot set ratio while studying or processing in non-RT mode" << endl; return; } } @@ -263,11 +265,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; + cerr << "R2Stretcher::setPitchScale: Cannot set ratio while studying or processing in non-RT mode" << endl; return; } } @@ -281,7 +283,7 @@ RubberBandStretcher::Impl::setPitchScale(double fs) reconfigure(); - if (!(m_options & OptionPitchHighConsistency) && + if (!(m_options & RubberBandStretcher::OptionPitchHighConsistency) && (was1 || resampleBeforeStretching() != rbs) && m_pitchScale != 1.f) { @@ -295,19 +297,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; @@ -316,7 +318,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; @@ -325,15 +327,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; + cerr << "R2Stretcher::setKeyFrameMap: Cannot specify key frame map in RT mode" << endl; return; } if (m_mode == Processing) { - cerr << "RubberBandStretcher::Impl::setKeyFrameMap: Cannot specify key frame map after process() has begun" << endl; + cerr << "R2Stretcher::setKeyFrameMap: Cannot specify key frame map after process() has begun" << endl; return; } @@ -343,7 +345,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; @@ -354,7 +356,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; @@ -364,7 +366,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 @@ -381,7 +383,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; @@ -391,7 +393,7 @@ RubberBandStretcher::Impl::roundUp(size_t value) } void -RubberBandStretcher::Impl::calculateSizes() +R2Stretcher::calculateSizes() { size_t inputIncrement = m_defaultIncrement; size_t windowSize = m_baseFftSize; @@ -511,7 +513,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 { @@ -565,7 +567,7 @@ RubberBandStretcher::Impl::calculateSizes() } void -RubberBandStretcher::Impl::configure() +R2Stretcher::configure() { if (m_debugLevel > 0) { std::cerr << "configure[" << this << "]: realtime = " << m_realtime << ", pitch scale = " @@ -652,7 +654,7 @@ RubberBandStretcher::Impl::configure() } if (m_pitchScale != 1.0 || - (m_options & OptionPitchHighConsistency) || + (m_options & RubberBandStretcher::OptionPitchHighConsistency) || m_realtime) { for (size_t c = 0; c < m_channels; ++c) { @@ -698,7 +700,7 @@ RubberBandStretcher::Impl::configure() delete m_stretchCalculator; m_stretchCalculator = new StretchCalculator (m_sampleRate, m_increment, - !(m_options & OptionTransientsSmooth)); + !(m_options & RubberBandStretcher::OptionTransientsSmooth)); m_stretchCalculator->setDebugLevel(m_debugLevel); m_inputDuration = 0; @@ -727,7 +729,7 @@ RubberBandStretcher::Impl::configure() void -RubberBandStretcher::Impl::reconfigure() +R2Stretcher::reconfigure() { if (!m_realtime) { if (m_mode == Studying) { @@ -836,43 +838,50 @@ RubberBandStretcher::Impl::reconfigure() } 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; + cerr << "R2Stretcher::setTransientsOption: Not permissible in non-realtime mode" << endl; 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; + cerr << "R2Stretcher::setDetectorOption: Not permissible in non-realtime mode" << endl; 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; @@ -883,36 +892,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; + cerr << "R2Stretcher::setPitchOption: Pitch option is not used in non-RT mode" << endl; 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; @@ -921,19 +932,19 @@ 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; + cerr << "R2Stretcher::study: Not meaningful in realtime mode" << endl; } return; } if (m_mode == Processing || m_mode == Finished) { - cerr << "RubberBandStretcher::Impl::study: Cannot study after processing" << endl; + cerr << "R2Stretcher::study: Cannot study after processing" << endl; return; } m_mode = Studying; @@ -1071,7 +1082,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; @@ -1085,7 +1096,7 @@ RubberBandStretcher::Impl::getOutputIncrements() const } vector -RubberBandStretcher::Impl::getPhaseResetCurve() const +R2Stretcher::getPhaseResetCurve() const { if (!m_realtime) { return m_phaseResetDf; @@ -1099,7 +1110,7 @@ RubberBandStretcher::Impl::getPhaseResetCurve() const } vector -RubberBandStretcher::Impl::getExactTimePoints() const +R2Stretcher::getExactTimePoints() const { std::vector points; if (!m_realtime) { @@ -1113,9 +1124,9 @@ RubberBandStretcher::Impl::getExactTimePoints() const } void -RubberBandStretcher::Impl::calculateStretch() +R2Stretcher::calculateStretch() { - Profiler profiler("RubberBandStretcher::Impl::calculateStretch"); + Profiler profiler("R2Stretcher::calculateStretch"); size_t inputDuration = m_inputDuration; @@ -1156,16 +1167,16 @@ RubberBandStretcher::Impl::calculateStretch() } void -RubberBandStretcher::Impl::setDebugLevel(int level) +R2Stretcher::setDebugLevel(int level) { m_debugLevel = 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; @@ -1214,12 +1225,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; + cerr << "R2Stretcher::process: Cannot process again after final chunk" << endl; return; } diff --git a/src/faster/StretcherImpl.h b/src/faster/R2Stretcher.h similarity index 92% rename from src/faster/StretcherImpl.h rename to src/faster/R2Stretcher.h index 98be6fc..06efbcb 100644 --- a/src/faster/StretcherImpl.h +++ b/src/faster/R2Stretcher.h @@ -42,21 +42,16 @@ 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); + ~R2Stretcher(); void reset(); void setTimeRatio(double ratio); @@ -67,11 +62,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); @@ -178,7 +173,7 @@ protected: #endif bool m_realtime; - Options m_options; + RubberBandStretcher::Options m_options; int m_debugLevel; enum ProcessMode { @@ -203,12 +198,12 @@ protected: class ProcessThread : public Thread { public: - ProcessThread(Impl *s, size_t c); + ProcessThread(R2Stretcher *s, size_t c); void run(); void signalDataAvailable(); void abandon(); private: - Impl *m_s; + R2Stretcher *m_s; size_t m_channel; Condition m_dataAvailable; bool m_abandoning; diff --git a/src/faster/StretcherChannelData.cpp b/src/faster/StretcherChannelData.cpp index 3d379c6..43af2d2 100644 --- a/src/faster/StretcherChannelData.cpp +++ b/src/faster/StretcherChannelData.cpp @@ -31,27 +31,27 @@ 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; @@ -114,7 +114,7 @@ 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; @@ -206,7 +206,7 @@ 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(); @@ -224,13 +224,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; @@ -259,7 +259,7 @@ RubberBandStretcher::Impl::ChannelData::~ChannelData() } void -RubberBandStretcher::Impl::ChannelData::reset() +R2Stretcher::ChannelData::reset() { inbuf->reset(); outbuf->reset(); diff --git a/src/faster/StretcherChannelData.h b/src/faster/StretcherChannelData.h index ab23f41..1b2d673 100644 --- a/src/faster/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/faster/StretcherProcess.cpp b/src/faster/StretcherProcess.cpp index c870bef..64ebd07 100644 --- a/src/faster/StretcherProcess.cpp +++ b/src/faster/StretcherProcess.cpp @@ -21,7 +21,7 @@ you must obtain a valid commercial licence before doing so. */ -#include "StretcherImpl.h" +#include "R2Stretcher.h" #include "StretcherChannelData.h" #include "../common/StretchCalculator.h" @@ -47,7 +47,7 @@ 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)), @@ -55,7 +55,7 @@ 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; @@ -108,7 +108,7 @@ RubberBandStretcher::Impl::ProcessThread::run() } void -RubberBandStretcher::Impl::ProcessThread::signalDataAvailable() +R2Stretcher::ProcessThread::signalDataAvailable() { m_dataAvailable.lock(); m_dataAvailable.signal(); @@ -116,7 +116,7 @@ RubberBandStretcher::Impl::ProcessThread::signalDataAvailable() } void -RubberBandStretcher::Impl::ProcessThread::abandon() +R2Stretcher::ProcessThread::abandon() { m_abandoning = true; } @@ -124,16 +124,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 @@ -141,7 +141,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, @@ -161,13 +161,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; @@ -179,13 +179,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) { @@ -204,7 +205,7 @@ 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 " + cerr << "WARNING: R2Stretcher::consumeChannel: resizing resampler buffer from " << cd.resamplebufSize << " to " << reqSize << endl; cd.setResampleBufSize(reqSize); } @@ -265,9 +266,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 @@ -338,9 +339,9 @@ RubberBandStretcher::Impl::processChunks(size_t c, bool &any, bool &last) } 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 @@ -381,9 +382,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; @@ -437,12 +438,12 @@ 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 @@ -541,11 +542,11 @@ 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; @@ -571,7 +572,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; + cerr << "ERROR: R2Stretcher::calculateIncrements: Channels are not in sync" << endl; return; } } @@ -676,12 +677,12 @@ RubberBandStretcher::Impl::calculateIncrements(size_t &phaseIncrementRtn, } 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; @@ -710,7 +711,7 @@ RubberBandStretcher::Impl::getIncrements(size_t channel, bool gotData = true; if (cd.chunkCount >= m_outputIncrements.size()) { -// cerr << "WARNING: RubberBandStretcher::Impl::getIncrements:" +// cerr << "WARNING: R2Stretcher::getIncrements:" // << " chunk count " << cd.chunkCount << " >= " // << m_outputIncrements.size() << endl; if (m_outputIncrements.size() == 0) { @@ -741,7 +742,7 @@ RubberBandStretcher::Impl::getIncrements(size_t channel, } /* if (shiftIncrement >= int(m_windowSize)) { - cerr << "*** ERROR: RubberBandStretcher::Impl::processChunks: shiftIncrement " << shiftIncrement << " >= windowSize " << m_windowSize << " at " << cd.chunkCount << " (of " << m_outputIncrements.size() << ")" << endl; + cerr << "*** ERROR: R2Stretcher::processChunks: shiftIncrement " << shiftIncrement << " >= windowSize " << m_windowSize << " at " << cd.chunkCount << " (of " << m_outputIncrements.size() << ")" << endl; shiftIncrement = m_windowSize; } */ @@ -752,9 +753,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]; @@ -773,11 +774,11 @@ 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]; @@ -790,8 +791,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); @@ -917,9 +918,9 @@ RubberBandStretcher::Impl::modifyChunk(size_t 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]; @@ -977,13 +978,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); } @@ -1049,9 +1049,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]; @@ -1077,10 +1077,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) { @@ -1089,7 +1090,7 @@ 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 " + cerr << "WARNING: R2Stretcher::writeChunk: resizing resampler buffer from " << cd.resamplebufSize << " to " << reqSize << endl; cd.setResampleBufSize(reqSize); } @@ -1134,7 +1135,7 @@ RubberBandStretcher::Impl::writeChunk(size_t channel, size_t shiftIncrement, boo cd.accumulatorFill = 0; if (cd.draining) { if (m_debugLevel > 1) { - cerr << "RubberBandStretcher::Impl::processChunks: setting outputComplete to true" << endl; + cerr << "R2Stretcher::processChunks: setting outputComplete to true" << endl; } cd.outputComplete = true; } @@ -1142,9 +1143,9 @@ RubberBandStretcher::Impl::writeChunk(size_t channel, size_t shiftIncrement, boo } 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 @@ -1183,7 +1184,7 @@ RubberBandStretcher::Impl::writeOutput(RingBuffer &to, float *from, size_ size_t written = to.write(from, qty); if (written < qty) { - cerr << "WARNING: RubberBandStretcher::Impl::writeOutput: " + cerr << "WARNING: R2Stretcher::writeOutput: " << "Buffer overrun on output: wrote " << written << " of " << qty << " samples" << endl; } @@ -1216,9 +1217,9 @@ RubberBandStretcher::Impl::writeOutput(RingBuffer &to, float *from, size_ } int -RubberBandStretcher::Impl::available() const +R2Stretcher::available() const { - Profiler profiler("RubberBandStretcher::Impl::available"); + Profiler profiler("R2Stretcher::available"); #ifndef NO_THREADING if (m_threaded) { @@ -1242,7 +1243,7 @@ RubberBandStretcher::Impl::available() const //!!! 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); } } } @@ -1273,9 +1274,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; @@ -1284,14 +1285,15 @@ RubberBandStretcher::Impl::retrieve(float *const *output, size_t samples) const if (gotHere < got) { if (c > 0) { if (m_debugLevel > 0) { - cerr << "RubberBandStretcher::Impl::retrieve: WARNING: channel imbalance detected" << endl; + cerr << "R2Stretcher::retrieve: WARNING: channel imbalance detected" << endl; } } 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/Guide.h b/src/finer/Guide.h index 2e07c4e..f71148b 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -124,8 +124,9 @@ public: m_configuration.fftBandLimits[0] = BandLimits(bandFftSize, rate, 0.0, m_maxLower); - // This is the classification and fallback FFT: we need the - // full range for it + // 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 bandFftSize = roundUp(int(ceil(rate/32.0))); m_configuration.fftBandLimits[1] = BandLimits(bandFftSize, rate, 0.0, rate / 2.0); diff --git a/src/finer/R3StretcherImpl.cpp b/src/finer/R3Stretcher.cpp similarity index 92% rename from src/finer/R3StretcherImpl.cpp rename to src/finer/R3Stretcher.cpp index e349243..57b8f6a 100644 --- a/src/finer/R3StretcherImpl.cpp +++ b/src/finer/R3Stretcher.cpp @@ -21,7 +21,7 @@ you must obtain a valid commercial licence before doing so. */ -#include "R3StretcherImpl.h" +#include "R3Stretcher.h" #include "../common/VectorOpsComplex.h" @@ -29,9 +29,9 @@ namespace RubberBand { -R3StretcherImpl::R3StretcherImpl(Parameters parameters, - double initialTimeRatio, - double initialPitchScale) : +R3Stretcher::R3Stretcher(Parameters parameters, + double initialTimeRatio, + double initialPitchScale) : m_parameters(parameters), m_timeRatio(initialTimeRatio), m_pitchScale(initialPitchScale), @@ -144,39 +144,39 @@ R3StretcherImpl::R3StretcherImpl(Parameters parameters, } WindowType -R3StretcherImpl::ScaleData::analysisWindowShape(int fftSize) +R3Stretcher::ScaleData::analysisWindowShape(int fftSize) { if (fftSize > 2048) return HannWindow; else return NiemitaloForwardWindow; } int -R3StretcherImpl::ScaleData::analysisWindowLength(int fftSize) +R3Stretcher::ScaleData::analysisWindowLength(int fftSize) { return fftSize; } WindowType -R3StretcherImpl::ScaleData::synthesisWindowShape(int fftSize) +R3Stretcher::ScaleData::synthesisWindowShape(int fftSize) { if (fftSize > 2048) return HannWindow; else return NiemitaloReverseWindow; } int -R3StretcherImpl::ScaleData::synthesisWindowLength(int fftSize) +R3Stretcher::ScaleData::synthesisWindowLength(int fftSize) { if (fftSize > 2048) return fftSize/2; else return fftSize; } void -R3StretcherImpl::setTimeRatio(double ratio) +R3Stretcher::setTimeRatio(double ratio) { if (!isRealTime()) { if (m_mode == ProcessMode::Studying || m_mode == ProcessMode::Processing) { - m_parameters.logger("R3StretcherImpl::setTimeRatio: Cannot set time ratio while studying or processing in non-RT mode"); + m_parameters.logger("R3Stretcher::setTimeRatio: Cannot set time ratio while studying or processing in non-RT mode"); return; } } @@ -187,12 +187,12 @@ R3StretcherImpl::setTimeRatio(double ratio) } void -R3StretcherImpl::setPitchScale(double scale) +R3Stretcher::setPitchScale(double scale) { if (!isRealTime()) { if (m_mode == ProcessMode::Studying || m_mode == ProcessMode::Processing) { - m_parameters.logger("R3StretcherImpl::setTimeRatio: Cannot set pitch scale while studying or processing in non-RT mode"); + m_parameters.logger("R3Stretcher::setTimeRatio: Cannot set pitch scale while studying or processing in non-RT mode"); return; } } @@ -203,12 +203,12 @@ R3StretcherImpl::setPitchScale(double scale) } void -R3StretcherImpl::setFormantScale(double scale) +R3Stretcher::setFormantScale(double scale) { if (!isRealTime()) { if (m_mode == ProcessMode::Studying || m_mode == ProcessMode::Processing) { - m_parameters.logger("R3StretcherImpl::setTimeRatio: Cannot set formant scale while studying or processing in non-RT mode"); + m_parameters.logger("R3Stretcher::setTimeRatio: Cannot set formant scale while studying or processing in non-RT mode"); return; } } @@ -217,7 +217,7 @@ R3StretcherImpl::setFormantScale(double scale) } void -R3StretcherImpl::setFormantOption(RubberBandStretcher::Options options) +R3Stretcher::setFormantOption(RubberBandStretcher::Options options) { int mask = (RubberBandStretcher::OptionFormantShifted | RubberBandStretcher::OptionFormantPreserved); @@ -227,14 +227,14 @@ R3StretcherImpl::setFormantOption(RubberBandStretcher::Options options) } void -R3StretcherImpl::setKeyFrameMap(const std::map &mapping) +R3Stretcher::setKeyFrameMap(const std::map &mapping) { if (isRealTime()) { - m_parameters.logger("R3StretcherImpl::setKeyFrameMap: Cannot specify key frame map in RT mode"); + m_parameters.logger("R3Stretcher::setKeyFrameMap: Cannot specify key frame map in RT mode"); return; } if (m_mode == ProcessMode::Processing || m_mode == ProcessMode::Finished) { - m_parameters.logger("R3StretcherImpl::setKeyFrameMap: Cannot specify key frame map after process() has begun"); + m_parameters.logger("R3Stretcher::setKeyFrameMap: Cannot specify key frame map after process() has begun"); return; } @@ -242,7 +242,7 @@ R3StretcherImpl::setKeyFrameMap(const std::map &mapping) } void -R3StretcherImpl::calculateHop() +R3Stretcher::calculateHop() { double ratio = getEffectiveRatio(); @@ -281,11 +281,11 @@ R3StretcherImpl::calculateHop() m_inhop = int(floor(inhop)); - std::cout << "R3StretcherImpl::calculateHop: inhop = " << m_inhop << ", proposed outhop = " << proposedOuthop << ", mean outhop = " << m_inhop * ratio << std::endl; + std::cout << "R3Stretcher::calculateHop: inhop = " << m_inhop << ", proposed outhop = " << proposedOuthop << ", mean outhop = " << m_inhop * ratio << std::endl; } void -R3StretcherImpl::updateRatioFromMap() +R3Stretcher::updateRatioFromMap() { if (m_keyFrameMap.empty()) return; @@ -344,25 +344,25 @@ R3StretcherImpl::updateRatioFromMap() } double -R3StretcherImpl::getTimeRatio() const +R3Stretcher::getTimeRatio() const { return m_timeRatio; } double -R3StretcherImpl::getPitchScale() const +R3Stretcher::getPitchScale() const { return m_pitchScale; } double -R3StretcherImpl::getFormantScale() const +R3Stretcher::getFormantScale() const { return m_formantScale; } size_t -R3StretcherImpl::getLatency() const +R3Stretcher::getLatency() const { if (!isRealTime()) { return 0; @@ -373,13 +373,13 @@ R3StretcherImpl::getLatency() const } size_t -R3StretcherImpl::getChannelCount() const +R3Stretcher::getChannelCount() const { return m_parameters.channels; } void -R3StretcherImpl::reset() +R3Stretcher::reset() { m_calculator->reset(); m_resampler->reset(); @@ -406,15 +406,15 @@ R3StretcherImpl::reset() } void -R3StretcherImpl::study(const float *const *, size_t samples, bool) +R3Stretcher::study(const float *const *, size_t samples, bool) { if (isRealTime()) { - m_parameters.logger("R3StretcherImpl::study: Not meaningful in realtime mode"); + m_parameters.logger("R3Stretcher::study: Not meaningful in realtime mode"); return; } if (m_mode == ProcessMode::Processing || m_mode == ProcessMode::Finished) { - m_parameters.logger("R3StretcherImpl::study: Cannot study after processing"); + m_parameters.logger("R3Stretcher::study: Cannot study after processing"); return; } @@ -427,7 +427,7 @@ R3StretcherImpl::study(const float *const *, size_t samples, bool) } size_t -R3StretcherImpl::getSamplesRequired() const +R3Stretcher::getSamplesRequired() const { if (available() != 0) return 0; int longest = m_guideConfiguration.longestFftSize; @@ -440,10 +440,10 @@ R3StretcherImpl::getSamplesRequired() const } void -R3StretcherImpl::process(const float *const *input, size_t samples, bool final) +R3Stretcher::process(const float *const *input, size_t samples, bool final) { if (m_mode == ProcessMode::Finished) { - m_parameters.logger("R3StretcherImpl::process: Cannot process again after final chunk"); + m_parameters.logger("R3Stretcher::process: Cannot process again after final chunk"); return; } @@ -468,7 +468,7 @@ R3StretcherImpl::process(const float *const *input, size_t samples, bool final) size_t ws = m_channelData[0]->inbuf->getWriteSpace(); if (samples > ws) { //!!! check this - m_parameters.logger("R3StretcherImpl::process: WARNING: Forced to increase input buffer size. Either setMaxProcessSize was not properly called or process is being called repeatedly without retrieve."); + m_parameters.logger("R3Stretcher::process: WARNING: Forced to increase input buffer size. Either setMaxProcessSize was not properly called or process is being called repeatedly without retrieve."); 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); @@ -486,7 +486,7 @@ R3StretcherImpl::process(const float *const *input, size_t samples, bool final) } int -R3StretcherImpl::available() const +R3Stretcher::available() const { int av = int(m_channelData[0]->outbuf->getReadSpace()); if (av == 0 && m_mode == ProcessMode::Finished) { @@ -497,7 +497,7 @@ R3StretcherImpl::available() const } size_t -R3StretcherImpl::retrieve(float *const *output, size_t samples) const +R3Stretcher::retrieve(float *const *output, size_t samples) const { int got = samples; @@ -505,7 +505,7 @@ R3StretcherImpl::retrieve(float *const *output, size_t samples) const int gotHere = m_channelData[c]->outbuf->read(output[c], got); if (gotHere < got) { if (c > 0) { - m_parameters.logger("R3StretcherImpl::retrieve: WARNING: channel imbalance detected"); + m_parameters.logger("R3Stretcher::retrieve: WARNING: channel imbalance detected"); } got = std::min(got, std::max(gotHere, 0)); } @@ -515,7 +515,7 @@ R3StretcherImpl::retrieve(float *const *output, size_t samples) const } void -R3StretcherImpl::consume() +R3Stretcher::consume() { int longest = m_guideConfiguration.longestFftSize; int channels = m_parameters.channels; @@ -673,7 +673,7 @@ R3StretcherImpl::consume() } void -R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) +R3Stretcher::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) { int longest = m_guideConfiguration.longestFftSize; int classify = m_guideConfiguration.classificationFftSize; @@ -908,7 +908,7 @@ R3StretcherImpl::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) } void -R3StretcherImpl::analyseFormant(int c) +R3Stretcher::analyseFormant(int c) { auto &cd = m_channelData.at(c); auto &f = *cd->formant; @@ -942,7 +942,7 @@ R3StretcherImpl::analyseFormant(int c) } void -R3StretcherImpl::adjustFormant(int c) +R3Stretcher::adjustFormant(int c) { auto &cd = m_channelData.at(c); @@ -976,7 +976,7 @@ R3StretcherImpl::adjustFormant(int c) } void -R3StretcherImpl::adjustPreKick(int c) +R3Stretcher::adjustPreKick(int c) { auto &cd = m_channelData.at(c); auto fftSize = cd->guidance.fftBands[0].fftSize; @@ -1007,7 +1007,7 @@ R3StretcherImpl::adjustPreKick(int c) } void -R3StretcherImpl::synthesiseChannel(int c, int outhop) +R3Stretcher::synthesiseChannel(int c, int outhop) { int longest = m_guideConfiguration.longestFftSize; diff --git a/src/finer/R3StretcherImpl.h b/src/finer/R3Stretcher.h similarity index 98% rename from src/finer/R3StretcherImpl.h rename to src/finer/R3Stretcher.h index bc23d70..6ba2944 100644 --- a/src/finer/R3StretcherImpl.h +++ b/src/finer/R3Stretcher.h @@ -46,7 +46,7 @@ namespace RubberBand { -class R3StretcherImpl +class R3Stretcher { public: struct Parameters { @@ -61,10 +61,10 @@ public: logger(_log) { } }; - R3StretcherImpl(Parameters parameters, - double initialTimeRatio, - double initialPitchScale); - ~R3StretcherImpl() { } + R3Stretcher(Parameters parameters, + double initialTimeRatio, + double initialPitchScale); + ~R3Stretcher() { } void reset(); From 35653f3a8613c3cc361b1c0299a832c2852e330e Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 21 Jun 2022 16:06:16 +0100 Subject: [PATCH 136/184] Start reworking log output --- meson.build | 5 +- rubberband/RubberBandStretcher.h | 34 ++++++++++- src/RubberBandStretcher.cpp | 45 +++++++++++++-- src/common/Log.h | 99 ++++++++++++++++++++++++++++++++ src/common/StretchCalculator.cpp | 6 +- src/common/StretchCalculator.h | 8 ++- src/faster/R2Stretcher.cpp | 7 ++- src/faster/R2Stretcher.h | 9 ++- src/finer/Guide.h | 12 ++-- src/finer/PhaseAdvance.h | 20 ++++--- src/finer/R3Stretcher.cpp | 54 ++++++++--------- src/finer/R3Stretcher.h | 26 ++++----- vamp/RubberBandVampPlugin.cpp | 4 +- 13 files changed, 258 insertions(+), 71 deletions(-) create mode 100644 src/common/Log.h diff --git a/meson.build b/meson.build index 442e274..6aa8677 100644 --- a/meson.build +++ b/meson.build @@ -42,10 +42,11 @@ library_sources = [ '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/FFT.cpp', - 'src/common/Allocators.cpp', 'src/common/StretchCalculator.cpp', 'src/common/sysutils.cpp', 'src/common/Thread.cpp', diff --git a/rubberband/RubberBandStretcher.h b/rubberband/RubberBandStretcher.h index 60fc9d9..2771862 100644 --- a/rubberband/RubberBandStretcher.h +++ b/rubberband/RubberBandStretcher.h @@ -37,6 +37,8 @@ #include #include +#include +#include #include /** @@ -277,7 +279,6 @@ public: * provided for backward compatibility only. They are ignored by * the stretcher. */ - enum Option { OptionProcessOffline = 0x00000000, @@ -331,6 +332,13 @@ public: PercussiveOptions = 0x00102000 }; + struct Logger { + virtual void log0(const char *) = 0; + virtual void log1(const char *, double) = 0; + virtual void log2(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. @@ -361,6 +369,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 cout) 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 generated and 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(); /** diff --git a/src/RubberBandStretcher.cpp b/src/RubberBandStretcher.cpp index 6043217..bba65e1 100644 --- a/src/RubberBandStretcher.cpp +++ b/src/RubberBandStretcher.cpp @@ -30,18 +30,39 @@ class RubberBandStretcher::Impl { R2Stretcher *m_r2; R3Stretcher *m_r3; - + + Log makeRBLog(std::shared_ptr logger) { + if (logger) { + return Log( + [=](const char *message) { + logger->log0(message); + }, + [=](const char *message, double arg0) { + logger->log1(message, arg0); + }, + [=](const char *message, double arg0, double arg1) { + logger->log2(message, arg0, arg1); + } + ); + } else { + return Log::makeCoutLog(); + } + } + 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) + initialTimeRatio, initialPitchScale, + makeRBLog(logger)) : nullptr), m_r3 ((options & OptionEngineFiner) ? new R3Stretcher(R3Stretcher::Parameters (double(sampleRate), channels, options), - initialTimeRatio, initialPitchScale) + initialTimeRatio, initialPitchScale, + makeRBLog(logger)) : nullptr) { } @@ -272,13 +293,14 @@ public: setDebugLevel(int level) { if (m_r2) m_r2->setDebugLevel(level); + else m_r3->setDebugLevel(level); } static void setDefaultDebugLevel(int level) { - R2Stretcher::setDefaultDebugLevel(level); -//!!! R3Stretcher::setDefaultDebugLevel(level); + Log::setDefaultDebugLevel(level); + R2Stretcher::setDefaultDebugLevel(level); //!!! } }; @@ -287,7 +309,18 @@ RubberBandStretcher::RubberBandStretcher(size_t sampleRate, 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)) { } diff --git a/src/common/Log.h b/src/common/Log.h new file mode 100644 index 0000000..450c24d --- /dev/null +++ b/src/common/Log.h @@ -0,0 +1,99 @@ +/* -*- 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) : + m_log0(other.m_log0), + m_log1(other.m_log1), + m_log2(other.m_log2), + m_debugLevel(other.m_debugLevel) { } + + Log &operator=(const Log &other) { + m_log0 = other.m_log0; + m_log1 = other.m_log1; + m_log2 = other.m_log2; + m_debugLevel = other.m_debugLevel; + return *this; + } + + void setDebugLevel(int level) { m_debugLevel = level; } + int getDebugLevel() const { return m_debugLevel; } + + static void setDefaultDebugLevel(int level) { m_defaultDebugLevel = level; } + + void log0(int level, const char *message) const { + if (level <= m_debugLevel) m_log0(message); + } + void log1(int level, const char *message, double arg0) const { + if (level <= m_debugLevel) m_log1(message, arg0); + } + + //!!! On reflection, log2 is a dumb choice of name + void log2(int level, const char *message, double arg0, double arg1) const { + if (level <= m_debugLevel) m_log2(message, arg0, arg1); + } + + static Log makeCoutLog() { + return Log( + [](const char *message) { + std::cout << "RubberBand: " << message << "\n"; + }, + [](const char *message, double arg0) { + std::cout << "RubberBand: " << message + << ": " << arg0 << "\n"; + }, + [](const char *message, double arg0, double arg1) { + std::cout << "RubberBand: " << message + << ": (" << arg0 << "," << arg1 << ")" << "\n"; + } + ); + } + +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/StretchCalculator.cpp b/src/common/StretchCalculator.cpp index 25c440f..204ccd3 100644 --- a/src/common/StretchCalculator.cpp +++ b/src/common/StretchCalculator.cpp @@ -37,7 +37,8 @@ namespace RubberBand StretchCalculator::StretchCalculator(size_t sampleRate, size_t inputIncrement, - bool useHardPeaks) : + bool useHardPeaks, + Log log) : m_sampleRate(sampleRate), m_increment(inputIncrement), m_prevDf(0), @@ -49,7 +50,8 @@ StretchCalculator::StretchCalculator(size_t sampleRate, m_useHardPeaks(useHardPeaks), m_inFrameCounter(0), m_frameCheckpoint(0, 0), - m_outFrameCounter(0) + m_outFrameCounter(0), + m_log(log) { // std::cerr << "StretchCalculator::StretchCalculator: useHardPeaks = " << useHardPeaks << std::endl; } diff --git a/src/common/StretchCalculator.h b/src/common/StretchCalculator.h index e1f91fc..af12780 100644 --- a/src/common/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(); /** @@ -107,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/faster/R2Stretcher.cpp b/src/faster/R2Stretcher.cpp index 202f69a..70f2988 100644 --- a/src/faster/R2Stretcher.cpp +++ b/src/faster/R2Stretcher.cpp @@ -65,7 +65,8 @@ R2Stretcher::R2Stretcher(size_t sampleRate, size_t channels, RubberBandStretcher::Options options, double initialTimeRatio, - double initialPitchScale) : + double initialPitchScale, + Log log) : m_sampleRate(sampleRate), m_channels(channels), m_timeRatio(initialTimeRatio), @@ -82,6 +83,7 @@ R2Stretcher::R2Stretcher(size_t sampleRate, #endif m_realtime(false), m_options(options), + m_log(log), m_debugLevel(m_defaultDebugLevel), m_mode(JustCreated), m_awindow(0), @@ -700,7 +702,8 @@ R2Stretcher::configure() delete m_stretchCalculator; m_stretchCalculator = new StretchCalculator (m_sampleRate, m_increment, - !(m_options & RubberBandStretcher::OptionTransientsSmooth)); + !(m_options & RubberBandStretcher::OptionTransientsSmooth), + m_log); m_stretchCalculator->setDebugLevel(m_debugLevel); m_inputDuration = 0; diff --git a/src/faster/R2Stretcher.h b/src/faster/R2Stretcher.h index 06efbcb..74b711b 100644 --- a/src/faster/R2Stretcher.h +++ b/src/faster/R2Stretcher.h @@ -31,6 +31,7 @@ #include "../common/RingBuffer.h" #include "../common/Scavenger.h" #include "../common/Thread.h" +#include "../common/Log.h" #include "../common/sysutils.h" #include "SincWindow.h" @@ -50,7 +51,8 @@ class R2Stretcher public: R2Stretcher(size_t sampleRate, size_t channels, RubberBandStretcher::Options options, - double initialTimeRatio, double initialPitchScale); + double initialTimeRatio, double initialPitchScale, + Log log); ~R2Stretcher(); void reset(); @@ -174,7 +176,8 @@ protected: bool m_realtime; RubberBandStretcher::Options m_options; - int m_debugLevel; + Log m_log; + int m_debugLevel; //!!! to go once Log switch complete enum ProcessMode { JustCreated, @@ -251,7 +254,7 @@ protected: void writeOutput(RingBuffer &to, float *from, size_t qty, size_t &outCount, size_t theoreticalOut); - static int m_defaultDebugLevel; + static int m_defaultDebugLevel; //!!! to go static const size_t m_defaultIncrement; static const size_t m_defaultFftSize; }; diff --git a/src/finer/Guide.h b/src/finer/Guide.h index f71148b..8ac8ecc 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -24,6 +24,8 @@ #ifndef RUBBERBAND_GUIDE_H #define RUBBERBAND_GUIDE_H +#include "../common/Log.h" + #include #include @@ -102,15 +104,12 @@ public: struct Parameters { double sampleRate; - std::function logger; - Parameters(double _sampleRate, - std::function _log) : - sampleRate(_sampleRate), - logger(_log) { } + Parameters(double _sampleRate) : sampleRate(_sampleRate) { } }; - Guide(Parameters parameters) : + 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)))), @@ -334,6 +333,7 @@ public: protected: Parameters m_parameters; + Log m_log; Configuration m_configuration; double m_minLower; diff --git a/src/finer/PhaseAdvance.h b/src/finer/PhaseAdvance.h index 5fc6f99..0f23c7f 100644 --- a/src/finer/PhaseAdvance.h +++ b/src/finer/PhaseAdvance.h @@ -26,6 +26,7 @@ #include "Guide.h" +#include "../common/Log.h" #include "../common/mathmisc.h" #include @@ -41,15 +42,13 @@ public: int fftSize; double sampleRate; int channels; - std::function logger; - Parameters(int _fftSize, double _sampleRate, int _channels, - std::function _log) : - fftSize(_fftSize), sampleRate(_sampleRate), - channels(_channels), logger(_log) { } + Parameters(int _fftSize, double _sampleRate, int _channels) : + fftSize(_fftSize), sampleRate(_sampleRate), channels(_channels) { } }; - GuidedPhaseAdvance(Parameters parameters) : + GuidedPhaseAdvance(Parameters parameters, Log log) : m_parameters(parameters), + m_log(log), m_binCount(parameters.fftSize / 2 + 1), m_peakPicker(m_binCount), m_reported(false) { @@ -111,7 +110,7 @@ public: int lowest = configuration.fftBandLimits[myFftBand].b0min; int highest = configuration.fftBandLimits[myFftBand].b1max; - if (!m_reported) { + if (m_log.getDebugLevel() > 0 && !m_reported) { std::ostringstream ostr; ostr << "PhaseAdvance: fftSize = " << m_parameters.fftSize << ": bins = " << bs << ", channels = " << channels @@ -122,7 +121,7 @@ public: << "Hz), highest = " << highest << " (" << configuration.fftBandLimits[myFftBand].f1max << "Hz)" << std::endl; - m_parameters.logger(ostr.str()); + m_log.log0(1, ostr.str().c_str()); m_reported = true; } @@ -231,8 +230,13 @@ public: } } + void setDebugLevel(int debugLevel) { + m_log.setDebugLevel(debugLevel); + } + protected: Parameters m_parameters; + Log m_log; int m_binCount; Peak m_peakPicker; int **m_currentPeaks; diff --git a/src/finer/R3Stretcher.cpp b/src/finer/R3Stretcher.cpp index 57b8f6a..4c750da 100644 --- a/src/finer/R3Stretcher.cpp +++ b/src/finer/R3Stretcher.cpp @@ -31,12 +31,14 @@ namespace RubberBand { R3Stretcher::R3Stretcher(Parameters parameters, double initialTimeRatio, - double initialPitchScale) : + 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, parameters.logger)), + m_guide(Guide::Parameters(m_parameters.sampleRate), m_log), m_guideConfiguration(m_guide.getConfiguration()), m_channelAssembly(m_parameters.channels), m_inhop(1), @@ -83,18 +85,19 @@ R3Stretcher::R3Stretcher(Parameters parameters, (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_parameters.logger); - m_scaleData[fftSize] = std::make_shared(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 + 1, false, // no fixed inputIncrement + m_log)); Resampler::Parameters resamplerParameters; resamplerParameters.quality = Resampler::FastestTolerable; @@ -119,10 +122,10 @@ R3Stretcher::R3Stretcher(Parameters parameters, m_prevOuthop = int(round(m_inhop * getEffectiveRatio())); if (!m_inhop.is_lock_free()) { - m_parameters.logger("WARNING: std::atomic is not lock-free"); + m_log.log0(0, "WARNING: std::atomic is not lock-free"); } if (!m_timeRatio.is_lock_free()) { - m_parameters.logger("WARNING: std::atomic is not lock-free"); + m_log.log0(0, "WARNING: std::atomic is not lock-free"); } // Pad to half of the longest frame. As with R2, in real-time mode @@ -131,7 +134,7 @@ R3Stretcher::R3Stretcher(Parameters parameters, // changes. if (!isRealTime()) { - m_parameters.logger("Offline mode: pre-padding"); + m_log.log0(1, "Offline mode: pre-padding"); int pad = m_guideConfiguration.longestFftSize / 2; for (int c = 0; c < m_parameters.channels; ++c) { m_channelData[c]->inbuf->zero(pad); @@ -139,7 +142,7 @@ R3Stretcher::R3Stretcher(Parameters parameters, // By the time we skip this later we will have resampled m_startSkip = int(round(pad / m_pitchScale)); } else { - m_parameters.logger("RT mode: no internal pre-pad"); + m_log.log0(1, "RT mode: no internal pre-pad"); } } @@ -176,7 +179,7 @@ R3Stretcher::setTimeRatio(double ratio) if (!isRealTime()) { if (m_mode == ProcessMode::Studying || m_mode == ProcessMode::Processing) { - m_parameters.logger("R3Stretcher::setTimeRatio: Cannot set time ratio while studying or processing in non-RT mode"); + m_log.log0(0, "R3Stretcher::setTimeRatio: Cannot set time ratio while studying or processing in non-RT mode"); return; } } @@ -192,7 +195,7 @@ R3Stretcher::setPitchScale(double scale) if (!isRealTime()) { if (m_mode == ProcessMode::Studying || m_mode == ProcessMode::Processing) { - m_parameters.logger("R3Stretcher::setTimeRatio: Cannot set pitch scale while studying or processing in non-RT mode"); + m_log.log0(0, "R3Stretcher::setTimeRatio: Cannot set pitch scale while studying or processing in non-RT mode"); return; } } @@ -208,7 +211,7 @@ R3Stretcher::setFormantScale(double scale) if (!isRealTime()) { if (m_mode == ProcessMode::Studying || m_mode == ProcessMode::Processing) { - m_parameters.logger("R3Stretcher::setTimeRatio: Cannot set formant scale while studying or processing in non-RT mode"); + m_log.log0(0, "R3Stretcher::setTimeRatio: Cannot set formant scale while studying or processing in non-RT mode"); return; } } @@ -230,11 +233,11 @@ void R3Stretcher::setKeyFrameMap(const std::map &mapping) { if (isRealTime()) { - m_parameters.logger("R3Stretcher::setKeyFrameMap: Cannot specify key frame map in RT mode"); + m_log.log0(0, "R3Stretcher::setKeyFrameMap: Cannot specify key frame map in RT mode"); return; } if (m_mode == ProcessMode::Processing || m_mode == ProcessMode::Finished) { - m_parameters.logger("R3Stretcher::setKeyFrameMap: Cannot specify key frame map after process() has begun"); + m_log.log0(0, "R3Stretcher::setKeyFrameMap: Cannot specify key frame map after process() has begun"); return; } @@ -266,22 +269,21 @@ R3Stretcher::calculateHop() if (proposedOuthop > 512.0) proposedOuthop = 512.0; if (proposedOuthop < 128.0) proposedOuthop = 128.0; - std::cout << "calculateHop: for ratio " << ratio << " proposedOuthop = " - << proposedOuthop << std::endl; + m_log.log2(1, "calculateHop: ratio and proposed outhop", ratio, proposedOuthop); double inhop = proposedOuthop / ratio; if (inhop < 1.0) { - m_parameters.logger("WARNING: Extreme ratio yields ideal inhop < 1, results may be suspect"); + m_log.log2(0, "WARNING: Extreme ratio yields ideal inhop < 1, results may be suspect", ratio, inhop); inhop = 1.0; } if (inhop > 768.0) { - m_parameters.logger("WARNING: Extreme ratio yields ideal inhop > 768, results may be suspect"); + m_log.log2(0, "WARNING: Extreme ratio yields ideal inhop > 768, results may be suspect", ratio, inhop); inhop = 768.0; } m_inhop = int(floor(inhop)); - std::cout << "R3Stretcher::calculateHop: inhop = " << m_inhop << ", proposed outhop = " << proposedOuthop << ", mean outhop = " << m_inhop * ratio << std::endl; + m_log.log2(1, "calculateHop: inhop and mean outhop", m_inhop, m_inhop * ratio); } void @@ -409,12 +411,12 @@ void R3Stretcher::study(const float *const *, size_t samples, bool) { if (isRealTime()) { - m_parameters.logger("R3Stretcher::study: Not meaningful in realtime mode"); + m_log.log0(0, "R3Stretcher::study: Not meaningful in realtime mode"); return; } if (m_mode == ProcessMode::Processing || m_mode == ProcessMode::Finished) { - m_parameters.logger("R3Stretcher::study: Cannot study after processing"); + m_log.log0(0, "R3Stretcher::study: Cannot study after processing"); return; } @@ -443,7 +445,7 @@ void R3Stretcher::process(const float *const *input, size_t samples, bool final) { if (m_mode == ProcessMode::Finished) { - m_parameters.logger("R3Stretcher::process: Cannot process again after final chunk"); + m_log.log0(0, "R3Stretcher::process: Cannot process again after final chunk"); return; } @@ -468,7 +470,7 @@ R3Stretcher::process(const float *const *input, size_t samples, bool final) size_t ws = m_channelData[0]->inbuf->getWriteSpace(); if (samples > ws) { //!!! check this - m_parameters.logger("R3Stretcher::process: WARNING: Forced to increase input buffer size. Either setMaxProcessSize was not properly called or process is being called repeatedly without retrieve."); + m_log.log0(0, "R3Stretcher::process: WARNING: Forced to increase input buffer size. Either setMaxProcessSize was not properly called or process is being called repeatedly without retrieve."); 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); @@ -505,7 +507,7 @@ R3Stretcher::retrieve(float *const *output, size_t samples) const int gotHere = m_channelData[c]->outbuf->read(output[c], got); if (gotHere < got) { if (c > 0) { - m_parameters.logger("R3Stretcher::retrieve: WARNING: channel imbalance detected"); + m_log.log0(0, "R3Stretcher::retrieve: WARNING: channel imbalance detected"); } got = std::min(got, std::max(gotHere, 0)); } diff --git a/src/finer/R3Stretcher.h b/src/finer/R3Stretcher.h index 6ba2944..292f2c0 100644 --- a/src/finer/R3Stretcher.h +++ b/src/finer/R3Stretcher.h @@ -36,12 +36,12 @@ #include "../common/Allocators.h" #include "../common/Window.h" #include "../common/VectorOpsComplex.h" +#include "../common/Log.h" #include "../../rubberband/RubberBandStretcher.h" #include #include -#include namespace RubberBand { @@ -53,17 +53,15 @@ public: double sampleRate; int channels; RubberBandStretcher::Options options; - std::function logger; Parameters(double _sampleRate, int _channels, - RubberBandStretcher::Options _options, - std::function _log = &logCout) : - sampleRate(_sampleRate), channels(_channels), options(_options), - logger(_log) { } + RubberBandStretcher::Options _options) : + sampleRate(_sampleRate), channels(_channels), options(_options) { } }; R3Stretcher(Parameters parameters, double initialTimeRatio, - double initialPitchScale); + double initialPitchScale, + Log log); ~R3Stretcher() { } void reset(); @@ -89,6 +87,10 @@ public: size_t getLatency() const; size_t getChannelCount() const; + void setDebugLevel(int level) { + m_log.setDebugLevel(level); //!!! +others + } + protected: struct ClassificationReadaheadData { FixedVector timeDomain; @@ -242,7 +244,8 @@ protected: Window synthesisWindow; double windowScaleFactor; GuidedPhaseAdvance guided; - ScaleData(GuidedPhaseAdvance::Parameters guidedParameters) : + ScaleData(GuidedPhaseAdvance::Parameters guidedParameters, + Log log) : fftSize(guidedParameters.fftSize), fft(fftSize), analysisWindow(analysisWindowShape(fftSize), @@ -250,7 +253,7 @@ protected: synthesisWindow(synthesisWindowShape(fftSize), synthesisWindowLength(fftSize)), windowScaleFactor(0.0), - guided(guidedParameters) + guided(guidedParameters, log) { int asz = analysisWindow.getSize(), ssz = synthesisWindow.getSize(); int off = (asz - ssz) / 2; @@ -267,6 +270,7 @@ protected: }; Parameters m_parameters; + Log m_log; std::atomic m_timeRatio; std::atomic m_pitchScale; @@ -347,10 +351,6 @@ protected: return m_parameters.options & RubberBandStretcher::OptionProcessRealTime; } - - static void logCout(const std::string &message) { - std::cout << "RubberBandStretcher: " << message << std::endl; - } }; } diff --git a/vamp/RubberBandVampPlugin.cpp b/vamp/RubberBandVampPlugin.cpp index 6a8ca3a..a00ee1c 100644 --- a/vamp/RubberBandVampPlugin.cpp +++ b/vamp/RubberBandVampPlugin.cpp @@ -463,7 +463,9 @@ RubberBandVampPlugin::Impl::getRemainingFeaturesOffline() int rate = m_sampleRate; - RubberBand::StretchCalculator sc(rate, m_stretcher->getInputIncrement(), true); + RubberBand::StretchCalculator sc + (rate, m_stretcher->getInputIncrement(), true, + RubberBand::Log::makeCoutLog()); size_t inputIncrement = m_stretcher->getInputIncrement(); std::vector outputIncrements = m_stretcher->getOutputIncrements(); From 7434abe664e1e95e6c8852d7b7428e6b43ff7dbc Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 21 Jun 2022 17:03:24 +0100 Subject: [PATCH 137/184] More on logging --- src/common/Log.cpp | 30 ++++++++++++++++++++++++++++++ src/finer/R3Stretcher.cpp | 35 +++++++++++++++++++++-------------- 2 files changed, 51 insertions(+), 14 deletions(-) create mode 100644 src/common/Log.cpp diff --git a/src/common/Log.cpp b/src/common/Log.cpp new file mode 100644 index 0000000..7b4d32d --- /dev/null +++ b/src/common/Log.cpp @@ -0,0 +1,30 @@ +/* -*- 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 "Log.h" + +namespace RubberBand { + +int Log::m_defaultDebugLevel = 0; + +} diff --git a/src/finer/R3Stretcher.cpp b/src/finer/R3Stretcher.cpp index 4c750da..09b48db 100644 --- a/src/finer/R3Stretcher.cpp +++ b/src/finer/R3Stretcher.cpp @@ -90,8 +90,8 @@ R3Stretcher::R3Stretcher(Parameters parameters, int fftSize = band.fftSize; GuidedPhaseAdvance::Parameters guidedParameters (fftSize, m_parameters.sampleRate, m_parameters.channels); - m_scaleData[fftSize] = std::make_shared(guidedParameters, - m_log); + m_scaleData[fftSize] = std::make_shared + (guidedParameters, m_log); } m_calculator = std::unique_ptr @@ -294,7 +294,12 @@ R3Stretcher::updateRatioFromMap() if (m_processInputDuration == 0) { m_timeRatio = double(m_keyFrameMap.begin()->second) / double(m_keyFrameMap.begin()->first); - std::cout << "initial key-frame map entry " << m_keyFrameMap.begin()->first << " -> " << m_keyFrameMap.begin()->second << " gives initial ratio " << m_timeRatio << std::endl; + + m_log.log2(1, "initial key-frame map entry ", + double(m_keyFrameMap.begin()->first), + double(m_keyFrameMap.begin()->second)); + m_log.log1(1, "giving initial ratio ", m_timeRatio); + calculateHop(); m_lastKeyFrameSurpassed = 0; return; @@ -308,7 +313,8 @@ R3Stretcher::updateRatioFromMap() if (m_processInputDuration >= i0->first) { - std::cout << "at " << m_processInputDuration << " (output = " << m_totalOutputDuration << ") we have passed " << i0->first << ", looking ahead to next key frame" << std::endl; + m_log.log2(2, "input duration surpasses pending key frame", + double(m_processInputDuration), double(i0->first)); auto i1 = m_keyFrameMap.upper_bound(m_processInputDuration); @@ -333,10 +339,13 @@ R3Stretcher::updateRatioFromMap() double ratio = double(toKeyFrameAtOutput) / double(toKeyFrameAtInput); - std::cout << "keyFrameAtInput = " << keyFrameAtInput << ", keyFrameAtOutput = " << keyFrameAtOutput << std::endl; - std::cout << "currently at input = " << m_processInputDuration << ", currently at output = " << m_totalOutputDuration << std::endl; - std::cout << "toKeyFrameAtInput = " << toKeyFrameAtInput << ", toKeyFrameAtOutput = " << toKeyFrameAtOutput << std::endl; - std::cout << "ratio = " << ratio << std::endl; + m_log.log2(2, "next key frame input and output", + double(keyFrameAtInput), double(keyFrameAtOutput)); + m_log.log2(2, "current input and output", + double(m_processInputDuration), double(m_totalOutputDuration)); + m_log.log2(2, "to next key frame input and output", + double(toKeyFrameAtInput), double(toKeyFrameAtOutput)); + m_log.log1(2, "new ratio", ratio); m_timeRatio = ratio; calculateHop(); @@ -552,16 +561,14 @@ R3Stretcher::consume() // 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) { - std::cout << "Note: inhop has changed from " << m_prevInhop - << " to " << inhop << std::endl; + m_log.log2(2, "change in inhop", double(m_prevInhop), double(inhop)); } if (outhop != m_prevOuthop) { - std::cout << "Note: outhop has changed from " << m_prevOuthop - << " to " << outhop << std::endl; + m_log.log2(2, "change in outhop", double(m_prevOuthop), double(outhop)); } -*/ + while (m_channelData.at(0)->outbuf->getWriteSpace() >= outhop) { // NB our ChannelData, ScaleData, and ChannelScaleData maps From ad5b885debbbd425d76e02e7f88613b2eae437f5 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 21 Jun 2022 20:26:25 +0100 Subject: [PATCH 138/184] Further logging updates --- src/faster/R2Stretcher.cpp | 43 ++++++++++++++++---------------------- src/faster/R2Stretcher.h | 1 + 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/src/faster/R2Stretcher.cpp b/src/faster/R2Stretcher.cpp index 70f2988..87eb634 100644 --- a/src/faster/R2Stretcher.cpp +++ b/src/faster/R2Stretcher.cpp @@ -126,17 +126,13 @@ R2Stretcher::R2Stretcher(size_t sampleRate, (options & RubberBandStretcher::OptionWindowLong)) { if ((options & RubberBandStretcher::OptionWindowShort) && (options & RubberBandStretcher::OptionWindowLong)) { - cerr << "R2Stretcher::R2Stretcher: Cannot specify OptionWindowLong and OptionWindowShort together; falling back to OptionWindowStandard" << endl; + m_log.log0(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; - } + m_log.log1(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.log1(1, "setting baseFftSize", m_baseFftSize); } m_fftSize = m_baseFftSize; m_aWindowSize = m_baseFftSize; @@ -163,8 +159,8 @@ R2Stretcher::R2Stretcher(size_t sampleRate, m_threaded = false; } - if (m_threaded && m_debugLevel > 0) { - cerr << "Going multithreaded..." << endl; + if (m_threaded) { + m_log.log0(1, "Going multithreaded..."); } } #endif @@ -179,9 +175,7 @@ R2Stretcher::~R2Stretcher() 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.log1(1, "RubberBandStretcher::~RubberBandStretcher: joining for channel", (*i)->channel()); (*i)->abandon(); (*i)->wait(); delete *i; @@ -216,9 +210,7 @@ R2Stretcher::reset() 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.log1(1, "RubberBandStretcher::~RubberBandStretcher: joining for channel", (*i)->channel()); (*i)->abandon(); (*i)->wait(); delete *i; @@ -255,7 +247,7 @@ R2Stretcher::setTimeRatio(double ratio) { if (!m_realtime) { if (m_mode == Studying || m_mode == Processing) { - cerr << "R2Stretcher::setTimeRatio: Cannot set ratio while studying or processing in non-RT mode" << endl; + m_log.log0(0, "R2Stretcher::setTimeRatio: Cannot set ratio while studying or processing in non-RT mode"); return; } } @@ -271,7 +263,7 @@ R2Stretcher::setPitchScale(double fs) { if (!m_realtime) { if (m_mode == Studying || m_mode == Processing) { - cerr << "R2Stretcher::setPitchScale: Cannot set ratio while studying or processing in non-RT mode" << endl; + m_log.log0(0, "R2Stretcher::setPitchScale: Cannot set ratio while studying or processing in non-RT mode"); return; } } @@ -333,11 +325,11 @@ R2Stretcher::setKeyFrameMap(const std::map & mapping) { if (m_realtime) { - cerr << "R2Stretcher::setKeyFrameMap: Cannot specify key frame map in RT mode" << endl; + m_log.log0(0, "R2Stretcher::setKeyFrameMap: Cannot specify key frame map in RT mode"); return; } if (m_mode == Processing) { - cerr << "R2Stretcher::setKeyFrameMap: Cannot specify key frame map after process() has begun" << endl; + m_log.log0(0, "R2Stretcher::setKeyFrameMap: Cannot specify key frame map after process() has begun"); return; } @@ -405,12 +397,12 @@ R2Stretcher::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.log1(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.log1(0, "WARNING: Time ratio must be greater than zero! Resetting it to default, no time stretch will happen", m_timeRatio); m_timeRatio = 1.0; } @@ -532,10 +524,11 @@ R2Stretcher::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.log2(1, "calculateSizes: time ratio and pitch scale", m_timeRatio, m_pitchScale); + m_log.log1(1, "effective ratio", getEffectiveRatio()); + m_log.log2(1, "analysis and synthesis window sizes", m_aWindowSize, m_sWindowSize); + m_log.log1(1, "fft size", m_fftSize); + m_log.log2(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); diff --git a/src/faster/R2Stretcher.h b/src/faster/R2Stretcher.h index 74b711b..c3dca37 100644 --- a/src/faster/R2Stretcher.h +++ b/src/faster/R2Stretcher.h @@ -205,6 +205,7 @@ protected: void run(); void signalDataAvailable(); void abandon(); + size_t channel() { return m_channel; } private: R2Stretcher *m_s; size_t m_channel; From e8b63bd10d16d2b112ec70daf10c9ced56bda8a3 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 22 Jun 2022 09:10:02 +0100 Subject: [PATCH 139/184] These can just be overloads for log, which at least avoids us using log2 as a method name here --- rubberband/RubberBandStretcher.h | 6 ++-- src/RubberBandStretcher.cpp | 6 ++-- src/common/Log.cpp | 3 +- src/common/Log.h | 8 ++--- src/faster/R2Stretcher.cpp | 34 ++++++++++---------- src/finer/PhaseAdvance.h | 2 +- src/finer/R3Stretcher.cpp | 54 ++++++++++++++++---------------- 7 files changed, 56 insertions(+), 57 deletions(-) diff --git a/rubberband/RubberBandStretcher.h b/rubberband/RubberBandStretcher.h index 2771862..4be1bd0 100644 --- a/rubberband/RubberBandStretcher.h +++ b/rubberband/RubberBandStretcher.h @@ -333,9 +333,9 @@ public: }; struct Logger { - virtual void log0(const char *) = 0; - virtual void log1(const char *, double) = 0; - virtual void log2(const char *, double, double) = 0; + virtual void log(const char *) = 0; + virtual void log(const char *, double) = 0; + virtual void log(const char *, double, double) = 0; virtual ~Logger() { } }; diff --git a/src/RubberBandStretcher.cpp b/src/RubberBandStretcher.cpp index bba65e1..d183ecf 100644 --- a/src/RubberBandStretcher.cpp +++ b/src/RubberBandStretcher.cpp @@ -35,13 +35,13 @@ class RubberBandStretcher::Impl if (logger) { return Log( [=](const char *message) { - logger->log0(message); + logger->log(message); }, [=](const char *message, double arg0) { - logger->log1(message, arg0); + logger->log(message, arg0); }, [=](const char *message, double arg0, double arg1) { - logger->log2(message, arg0, arg1); + logger->log(message, arg0, arg1); } ); } else { diff --git a/src/common/Log.cpp b/src/common/Log.cpp index 7b4d32d..03b3536 100644 --- a/src/common/Log.cpp +++ b/src/common/Log.cpp @@ -23,7 +23,8 @@ #include "Log.h" -namespace RubberBand { +namespace RubberBand +{ int Log::m_defaultDebugLevel = 0; diff --git a/src/common/Log.h b/src/common/Log.h index 450c24d..d106270 100644 --- a/src/common/Log.h +++ b/src/common/Log.h @@ -58,15 +58,13 @@ public: static void setDefaultDebugLevel(int level) { m_defaultDebugLevel = level; } - void log0(int level, const char *message) const { + void log(int level, const char *message) const { if (level <= m_debugLevel) m_log0(message); } - void log1(int level, const char *message, double arg0) const { + void log(int level, const char *message, double arg0) const { if (level <= m_debugLevel) m_log1(message, arg0); } - - //!!! On reflection, log2 is a dumb choice of name - void log2(int level, const char *message, double arg0, double arg1) const { + void log(int level, const char *message, double arg0, double arg1) const { if (level <= m_debugLevel) m_log2(message, arg0, arg1); } diff --git a/src/faster/R2Stretcher.cpp b/src/faster/R2Stretcher.cpp index 87eb634..9cf21e9 100644 --- a/src/faster/R2Stretcher.cpp +++ b/src/faster/R2Stretcher.cpp @@ -126,13 +126,13 @@ R2Stretcher::R2Stretcher(size_t sampleRate, (options & RubberBandStretcher::OptionWindowLong)) { if ((options & RubberBandStretcher::OptionWindowShort) && (options & RubberBandStretcher::OptionWindowLong)) { - m_log.log0(0, "R2Stretcher::R2Stretcher: Cannot specify OptionWindowLong and OptionWindowShort together; falling back to OptionWindowStandard"); + 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; - m_log.log1(1, "setting baseFftSize", m_baseFftSize); + m_log.log(1, "setting baseFftSize", m_baseFftSize); } else if (options & RubberBandStretcher::OptionWindowLong) { m_baseFftSize = m_baseFftSize * 2; - m_log.log1(1, "setting baseFftSize", m_baseFftSize); + m_log.log(1, "setting baseFftSize", m_baseFftSize); } m_fftSize = m_baseFftSize; m_aWindowSize = m_baseFftSize; @@ -160,7 +160,7 @@ R2Stretcher::R2Stretcher(size_t sampleRate, } if (m_threaded) { - m_log.log0(1, "Going multithreaded..."); + m_log.log(1, "Going multithreaded..."); } } #endif @@ -175,7 +175,7 @@ R2Stretcher::~R2Stretcher() MutexLocker locker(&m_threadSetMutex); for (set::iterator i = m_threadSet.begin(); i != m_threadSet.end(); ++i) { - m_log.log1(1, "RubberBandStretcher::~RubberBandStretcher: joining for channel", (*i)->channel()); + m_log.log(1, "RubberBandStretcher::~RubberBandStretcher: joining for channel", (*i)->channel()); (*i)->abandon(); (*i)->wait(); delete *i; @@ -210,7 +210,7 @@ R2Stretcher::reset() m_threadSetMutex.lock(); for (set::iterator i = m_threadSet.begin(); i != m_threadSet.end(); ++i) { - m_log.log1(1, "RubberBandStretcher::~RubberBandStretcher: joining for channel", (*i)->channel()); + m_log.log(1, "RubberBandStretcher::~RubberBandStretcher: joining for channel", (*i)->channel()); (*i)->abandon(); (*i)->wait(); delete *i; @@ -247,7 +247,7 @@ R2Stretcher::setTimeRatio(double ratio) { if (!m_realtime) { if (m_mode == Studying || m_mode == Processing) { - m_log.log0(0, "R2Stretcher::setTimeRatio: Cannot set ratio while studying or processing in non-RT mode"); + m_log.log(0, "R2Stretcher::setTimeRatio: Cannot set ratio while studying or processing in non-RT mode"); return; } } @@ -263,7 +263,7 @@ R2Stretcher::setPitchScale(double fs) { if (!m_realtime) { if (m_mode == Studying || m_mode == Processing) { - m_log.log0(0, "R2Stretcher::setPitchScale: Cannot set ratio while studying or processing in non-RT mode"); + m_log.log(0, "R2Stretcher::setPitchScale: Cannot set ratio while studying or processing in non-RT mode"); return; } } @@ -325,11 +325,11 @@ R2Stretcher::setKeyFrameMap(const std::map & mapping) { if (m_realtime) { - m_log.log0(0, "R2Stretcher::setKeyFrameMap: Cannot specify key frame map in RT mode"); + m_log.log(0, "R2Stretcher::setKeyFrameMap: Cannot specify key frame map in RT mode"); return; } if (m_mode == Processing) { - m_log.log0(0, "R2Stretcher::setKeyFrameMap: Cannot specify key frame map after process() has begun"); + m_log.log(0, "R2Stretcher::setKeyFrameMap: Cannot specify key frame map after process() has begun"); return; } @@ -397,12 +397,12 @@ R2Stretcher::calculateSizes() // This special case is likelier than one might hope, because // of naive initialisations in programs that set it from a // variable - m_log.log1(0, "WARNING: Pitch scale must be greater than zero! Resetting it to default, no pitch shift will happen", m_pitchScale); + 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 - m_log.log1(0, "WARNING: Time ratio must be greater than zero! Resetting it to default, no time stretch will happen", m_timeRatio); + 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; } @@ -524,11 +524,11 @@ R2Stretcher::calculateSizes() // twice the basic output increment (i.e. input increment times // ratio) for any chunk. - m_log.log2(1, "calculateSizes: time ratio and pitch scale", m_timeRatio, m_pitchScale); - m_log.log1(1, "effective ratio", getEffectiveRatio()); - m_log.log2(1, "analysis and synthesis window sizes", m_aWindowSize, m_sWindowSize); - m_log.log1(1, "fft size", m_fftSize); - m_log.log2(1, "input increment and mean output increment", m_increment, m_increment * getEffectiveRatio()); + 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); diff --git a/src/finer/PhaseAdvance.h b/src/finer/PhaseAdvance.h index 0f23c7f..11baf98 100644 --- a/src/finer/PhaseAdvance.h +++ b/src/finer/PhaseAdvance.h @@ -121,7 +121,7 @@ public: << "Hz), highest = " << highest << " (" << configuration.fftBandLimits[myFftBand].f1max << "Hz)" << std::endl; - m_log.log0(1, ostr.str().c_str()); + m_log.log(1, ostr.str().c_str()); m_reported = true; } diff --git a/src/finer/R3Stretcher.cpp b/src/finer/R3Stretcher.cpp index 09b48db..67bcaab 100644 --- a/src/finer/R3Stretcher.cpp +++ b/src/finer/R3Stretcher.cpp @@ -122,10 +122,10 @@ R3Stretcher::R3Stretcher(Parameters parameters, m_prevOuthop = int(round(m_inhop * getEffectiveRatio())); if (!m_inhop.is_lock_free()) { - m_log.log0(0, "WARNING: std::atomic is not lock-free"); + m_log.log(0, "WARNING: std::atomic is not lock-free"); } if (!m_timeRatio.is_lock_free()) { - m_log.log0(0, "WARNING: std::atomic is not lock-free"); + m_log.log(0, "WARNING: std::atomic is not lock-free"); } // Pad to half of the longest frame. As with R2, in real-time mode @@ -134,7 +134,7 @@ R3Stretcher::R3Stretcher(Parameters parameters, // changes. if (!isRealTime()) { - m_log.log0(1, "Offline mode: pre-padding"); + m_log.log(1, "Offline mode: pre-padding"); int pad = m_guideConfiguration.longestFftSize / 2; for (int c = 0; c < m_parameters.channels; ++c) { m_channelData[c]->inbuf->zero(pad); @@ -142,7 +142,7 @@ R3Stretcher::R3Stretcher(Parameters parameters, // By the time we skip this later we will have resampled m_startSkip = int(round(pad / m_pitchScale)); } else { - m_log.log0(1, "RT mode: no internal pre-pad"); + m_log.log(1, "RT mode: no internal pre-pad"); } } @@ -179,7 +179,7 @@ R3Stretcher::setTimeRatio(double ratio) if (!isRealTime()) { if (m_mode == ProcessMode::Studying || m_mode == ProcessMode::Processing) { - m_log.log0(0, "R3Stretcher::setTimeRatio: Cannot set time ratio while studying or processing in non-RT mode"); + m_log.log(0, "R3Stretcher::setTimeRatio: Cannot set time ratio while studying or processing in non-RT mode"); return; } } @@ -195,7 +195,7 @@ R3Stretcher::setPitchScale(double scale) if (!isRealTime()) { if (m_mode == ProcessMode::Studying || m_mode == ProcessMode::Processing) { - m_log.log0(0, "R3Stretcher::setTimeRatio: Cannot set pitch scale while studying or processing in non-RT mode"); + m_log.log(0, "R3Stretcher::setTimeRatio: Cannot set pitch scale while studying or processing in non-RT mode"); return; } } @@ -211,7 +211,7 @@ R3Stretcher::setFormantScale(double scale) if (!isRealTime()) { if (m_mode == ProcessMode::Studying || m_mode == ProcessMode::Processing) { - m_log.log0(0, "R3Stretcher::setTimeRatio: Cannot set formant scale while studying or processing in non-RT mode"); + m_log.log(0, "R3Stretcher::setTimeRatio: Cannot set formant scale while studying or processing in non-RT mode"); return; } } @@ -233,11 +233,11 @@ void R3Stretcher::setKeyFrameMap(const std::map &mapping) { if (isRealTime()) { - m_log.log0(0, "R3Stretcher::setKeyFrameMap: Cannot specify key frame map in RT mode"); + 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.log0(0, "R3Stretcher::setKeyFrameMap: Cannot specify key frame map after process() has begun"); + m_log.log(0, "R3Stretcher::setKeyFrameMap: Cannot specify key frame map after process() has begun"); return; } @@ -269,21 +269,21 @@ R3Stretcher::calculateHop() if (proposedOuthop > 512.0) proposedOuthop = 512.0; if (proposedOuthop < 128.0) proposedOuthop = 128.0; - m_log.log2(1, "calculateHop: ratio and proposed outhop", ratio, proposedOuthop); + m_log.log(1, "calculateHop: ratio and proposed outhop", ratio, proposedOuthop); double inhop = proposedOuthop / ratio; if (inhop < 1.0) { - m_log.log2(0, "WARNING: Extreme ratio yields ideal inhop < 1, results may be suspect", ratio, inhop); + 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.log2(0, "WARNING: Extreme ratio yields ideal inhop > 768, results may be suspect", ratio, inhop); + 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.log2(1, "calculateHop: inhop and mean outhop", m_inhop, m_inhop * ratio); + m_log.log(1, "calculateHop: inhop and mean outhop", m_inhop, m_inhop * ratio); } void @@ -295,10 +295,10 @@ R3Stretcher::updateRatioFromMap() m_timeRatio = double(m_keyFrameMap.begin()->second) / double(m_keyFrameMap.begin()->first); - m_log.log2(1, "initial key-frame map entry ", + m_log.log(1, "initial key-frame map entry ", double(m_keyFrameMap.begin()->first), double(m_keyFrameMap.begin()->second)); - m_log.log1(1, "giving initial ratio ", m_timeRatio); + m_log.log(1, "giving initial ratio ", m_timeRatio); calculateHop(); m_lastKeyFrameSurpassed = 0; @@ -313,7 +313,7 @@ R3Stretcher::updateRatioFromMap() if (m_processInputDuration >= i0->first) { - m_log.log2(2, "input duration surpasses pending key frame", + m_log.log(2, "input duration surpasses pending key frame", double(m_processInputDuration), double(i0->first)); auto i1 = m_keyFrameMap.upper_bound(m_processInputDuration); @@ -339,13 +339,13 @@ R3Stretcher::updateRatioFromMap() double ratio = double(toKeyFrameAtOutput) / double(toKeyFrameAtInput); - m_log.log2(2, "next key frame input and output", + m_log.log(2, "next key frame input and output", double(keyFrameAtInput), double(keyFrameAtOutput)); - m_log.log2(2, "current input and output", + m_log.log(2, "current input and output", double(m_processInputDuration), double(m_totalOutputDuration)); - m_log.log2(2, "to next key frame input and output", + m_log.log(2, "to next key frame input and output", double(toKeyFrameAtInput), double(toKeyFrameAtOutput)); - m_log.log1(2, "new ratio", ratio); + m_log.log(2, "new ratio", ratio); m_timeRatio = ratio; calculateHop(); @@ -420,12 +420,12 @@ void R3Stretcher::study(const float *const *, size_t samples, bool) { if (isRealTime()) { - m_log.log0(0, "R3Stretcher::study: Not meaningful in realtime mode"); + m_log.log(0, "R3Stretcher::study: Not meaningful in realtime mode"); return; } if (m_mode == ProcessMode::Processing || m_mode == ProcessMode::Finished) { - m_log.log0(0, "R3Stretcher::study: Cannot study after processing"); + m_log.log(0, "R3Stretcher::study: Cannot study after processing"); return; } @@ -454,7 +454,7 @@ void R3Stretcher::process(const float *const *input, size_t samples, bool final) { if (m_mode == ProcessMode::Finished) { - m_log.log0(0, "R3Stretcher::process: Cannot process again after final chunk"); + m_log.log(0, "R3Stretcher::process: Cannot process again after final chunk"); return; } @@ -479,7 +479,7 @@ R3Stretcher::process(const float *const *input, size_t samples, bool final) size_t ws = m_channelData[0]->inbuf->getWriteSpace(); if (samples > ws) { //!!! check this - m_log.log0(0, "R3Stretcher::process: WARNING: Forced to increase input buffer size. Either setMaxProcessSize was not properly called or process is being called repeatedly without retrieve."); + 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."); 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); @@ -516,7 +516,7 @@ R3Stretcher::retrieve(float *const *output, size_t samples) const int gotHere = m_channelData[c]->outbuf->read(output[c], got); if (gotHere < got) { if (c > 0) { - m_log.log0(0, "R3Stretcher::retrieve: WARNING: channel imbalance detected"); + m_log.log(0, "R3Stretcher::retrieve: WARNING: channel imbalance detected"); } got = std::min(got, std::max(gotHere, 0)); } @@ -563,10 +563,10 @@ R3Stretcher::consume() // values. if (inhop != m_prevInhop) { - m_log.log2(2, "change in inhop", double(m_prevInhop), double(inhop)); + m_log.log(2, "change in inhop", double(m_prevInhop), double(inhop)); } if (outhop != m_prevOuthop) { - m_log.log2(2, "change in outhop", double(m_prevOuthop), double(outhop)); + m_log.log(2, "change in outhop", double(m_prevOuthop), double(outhop)); } while (m_channelData.at(0)->outbuf->getWriteSpace() >= outhop) { From 5137b19407c95d8ecdb693efed31061876f874aa Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 22 Jun 2022 11:33:36 +0100 Subject: [PATCH 140/184] Wire up Log throughout --- src/common/Log.cpp | 23 +++ src/common/Log.h | 16 +- src/common/StretchCalculator.cpp | 236 +++++++++------------------- src/faster/R2Stretcher.cpp | 109 +++++-------- src/faster/StretcherChannelData.cpp | 8 - src/faster/StretcherProcess.cpp | 191 +++++++--------------- src/finer/R3Stretcher.cpp | 4 +- 7 files changed, 194 insertions(+), 393 deletions(-) diff --git a/src/common/Log.cpp b/src/common/Log.cpp index 03b3536..c2c944c 100644 --- a/src/common/Log.cpp +++ b/src/common/Log.cpp @@ -28,4 +28,27 @@ namespace RubberBand int Log::m_defaultDebugLevel = 0; +Log +Log::makeCoutLog() +{ + return Log( + [](const char *message) { + std::cout << "RubberBand: " << message << "\n"; + }, + [](const char *message, double arg0) { + auto prec = std::cout.precision(); + std::cout.precision(10); + std::cout << "RubberBand: " << message << ": " << arg0 << "\n"; + std::cout.precision(prec); + }, + [](const char *message, double arg0, double arg1) { + auto prec = std::cout.precision(); + std::cout.precision(10); + std::cout << "RubberBand: " << message + << ": (" << arg0 << ", " << arg1 << ")" << "\n"; + std::cout.precision(prec); + } + ); +} + } diff --git a/src/common/Log.h b/src/common/Log.h index d106270..1a9038c 100644 --- a/src/common/Log.h +++ b/src/common/Log.h @@ -68,21 +68,7 @@ public: if (level <= m_debugLevel) m_log2(message, arg0, arg1); } - static Log makeCoutLog() { - return Log( - [](const char *message) { - std::cout << "RubberBand: " << message << "\n"; - }, - [](const char *message, double arg0) { - std::cout << "RubberBand: " << message - << ": " << arg0 << "\n"; - }, - [](const char *message, double arg0, double arg1) { - std::cout << "RubberBand: " << message - << ": (" << arg0 << "," << arg1 << ")" << "\n"; - } - ); - } + static Log makeCoutLog(); private: std::function m_log0; diff --git a/src/common/StretchCalculator.cpp b/src/common/StretchCalculator.cpp index 204ccd3..7b98020 100644 --- a/src/common/StretchCalculator.cpp +++ b/src/common/StretchCalculator.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include "sysutils.h" @@ -53,7 +54,7 @@ StretchCalculator::StretchCalculator(size_t sampleRate, m_outFrameCounter(0), m_log(log) { -// std::cerr << "StretchCalculator::StretchCalculator: useHardPeaks = " << useHardPeaks << std::endl; + m_log.log(2, "StretchCalculator: useHardPeaks", useHardPeaks); } StretchCalculator::~StretchCalculator() @@ -85,25 +86,18 @@ StretchCalculator::calculate(double ratio, size_t inputDuration, size_t outputDuration = lrint(inputDuration * ratio); - if (m_debugLevel > 0) { - std::cerr << "StretchCalculator::calculate(): inputDuration " << inputDuration << ", ratio " << ratio << ", outputDuration " << outputDuration; - } + m_log.log(1, "StretchCalculator::calculate: inputDuration and ratio", inputDuration, ratio); 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; - } - + 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); - if (m_debugLevel > 1) { - std::cerr << "have " << peaks.size() << " fixed positions" << std::endl; - } + m_log.log(2, "have fixed positions", peaks.size()); size_t totalInput = 0, totalOutput = 0; @@ -124,7 +118,6 @@ StretchCalculator::calculate(double ratio, size_t inputDuration, } if (i == peaks.size()) { -// std::cerr << "note: i (=" << i << ") == peaks.size(); regionEndChunk " << regionEndChunk << " -> " << totalCount << ", regionEnd " << regionEnd << " -> " << outputDuration << std::endl; regionEndChunk = totalCount; regionEnd = outputDuration; } else { @@ -143,15 +136,12 @@ StretchCalculator::calculate(double ratio, size_t inputDuration, size_t regionDuration = regionEnd - regionStart; size_t nchunks = regionEndChunk - regionStartChunk; - - if (m_debugLevel > 1) { - std::cerr << "region from " << regionStartChunk << " to " << regionEndChunk << " (samples " << regionStart << " to " << regionEnd << ")" << std::endl; - } + + 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) { - if (m_debugLevel > 1) { - std::cerr << "note: nchunks == 0" << std::endl; - } + m_log.log(2, "note: nchunks == 0"); continue; } @@ -197,10 +187,10 @@ StretchCalculator::calculate(double ratio, size_t inputDuration, 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; - } - + 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; } @@ -239,8 +229,6 @@ StretchCalculator::mapPeaks(std::vector &peaks, while (mi != m_keyFrameMap.end()) { -// std::cerr << "mi->first is " << mi->first << ", second is " << mi->second < &peaks, 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; + 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; } @@ -276,9 +265,7 @@ StretchCalculator::mapPeaks(std::vector &peaks, peaks.push_back(p); targets.push_back(targetStartSample); - if (m_debugLevel > 1) { - std::cerr << "mapped chunk " << sourceStartChunk << " (frame " << sourceStartChunk * m_increment << ") -> " << targetStartSample << std::endl; - } + m_log.log(2, "mapped key-frame chunk to frame", sourceStartChunk, targetStartSample); while (peakidx < m_peaks.size()) { @@ -318,9 +305,7 @@ StretchCalculator::mapPeaks(std::vector &peaks, continue; } - if (m_debugLevel > 1) { - std::cerr << " peak chunk " << pchunk << " (frame " << pchunk * m_increment << ") -> " << target << std::endl; - } + m_log.log(2, "mapped peak chunk to frame", pchunk, target); peaks.push_back(p); targets.push_back(target); @@ -391,9 +376,7 @@ StretchCalculator::calculateSingle(double timeRatio, // 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; - } + m_log.log(2, "StretchCalculator: ratio changed from and to", m_prevRatio, ratio); int64_t toCheckpoint = expectedOutFrame (m_inFrameCounter, m_prevTimeRatio); @@ -404,23 +387,25 @@ StretchCalculator::calculateSingle(double timeRatio, 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; + 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"; - std::cerr << "inFrameCounter = " << m_inFrameCounter - << ", outFrameCounter = " << m_outFrameCounter - << std::endl; + os << "inFrameCounter = " << m_inFrameCounter + << ", outFrameCounter = " << m_outFrameCounter + << "\n"; - std::cerr << "The next sample out is input sample " << m_inFrameCounter << std::endl; + os << "The next sample out is input sample " << m_inFrameCounter << "\n"; + m_log.log(3, os.str().c_str()); } int64_t intended, projected; @@ -438,9 +423,8 @@ StretchCalculator::calculateSingle(double timeRatio, int64_t divergence = projected - intended; - if (m_debugLevel > 2) { - std::cerr << "for current frame + quarter frame: intended " << intended << ", projected " << projected << ", divergence " << divergence << std::endl; - } + 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 @@ -453,35 +437,26 @@ StretchCalculator::calculateSingle(double timeRatio, 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; - } + m_log.log(2, "StretchCalculator::calculateSingle: transient, but we're not permitting it because the divergence is too great", divergence); } else { isTransient = true; } } - if (m_debugLevel > 2) { - std::cerr << "df = " << df << ", prevDf = " << m_prevDf - << ", thresh = " << transientThreshold << std::endl; - } + m_log.log(3, "df and prevDf", df, m_prevDf); 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; - } + m_log.log(2, "StretchCalculator::calculateSingle: transient, but we have an amnesty: df and threshold", df, transientThreshold); isTransient = false; } --m_transientAmnesty; } if (isTransient) { - if (m_debugLevel > 1) { - std::cerr << "StretchCalculator::calculateSingle: transient at (df " << df << ", threshold " << transientThreshold << ")" << std::endl; - } + 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 = @@ -501,9 +476,10 @@ StretchCalculator::calculateSingle(double timeRatio, } int incr = lrint(outIncrement - recovery); - if (m_debugLevel > 2 || (m_debugLevel > 1 && divergence != 0)) { - std::cerr << "divergence = " << divergence << ", recovery = " << recovery << ", incr = " << incr << ", "; - } + + 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); @@ -514,25 +490,18 @@ StretchCalculator::calculateSingle(double timeRatio, incr = maxIncr; } - if (m_debugLevel > 2 || (m_debugLevel > 1 && divergence != 0)) { - std::cerr << "clamped into [" << minIncr << ", " << maxIncr - << "] becomes " << incr << std::endl; - } + m_log.log(level, "clamped into", minIncr, maxIncr); + m_log.log(level, "giving incr", incr); if (incr < 0) { - std::cerr << "WARNING: internal error: incr < 0 in calculateSingle" - << std::endl; + m_log.log(0, "WARNING: internal error: incr < 0 in calculateSingle"); outIncrement = 0; } else { outIncrement = incr; } } - if (m_debugLevel > 1) { - std::cerr << "StretchCalculator::calculateSingle: returning isTransient = " - << isTransient << ", outIncrement = " << outIncrement - << std::endl; - } + m_log.log(2, "StretchCalculator::calculateSingle: returning isTransient and outIncrement", isTransient, outIncrement); m_inFrameCounter += inIncrement; m_outFrameCounter += outIncrement * effectivePitchRatio; @@ -586,9 +555,7 @@ StretchCalculator::findPeaks(const std::vector &rawDf) (20 * double(m_increment)))); size_t prevHardPeak = 0; - if (m_debugLevel > 1) { - std::cerr << "hardPeakAmnesty = " << hardPeakAmnesty << std::endl; - } + m_log.log(2, "hardPeakAmnesty", hardPeakAmnesty); for (size_t i = 1; i + 1 < df.size(); ++i) { @@ -602,20 +569,16 @@ StretchCalculator::findPeaks(const std::vector &rawDf) } 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) { + 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_debugLevel > 1)) { - std::cerr << "hard peak at " << i << ": " << df[i] - << " > prev " << df[i-1] << " * 1.4" - << std::endl; + if (hard) { + m_log.log(2, "hard peak, single rise of 40%: chunk and df", i, df[i]); } } @@ -623,11 +586,8 @@ StretchCalculator::findPeaks(const std::vector &rawDf) 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) { + m_log.log(2, "hard peak, two rises of 20%: chunk and df", i, df[i]); } } @@ -637,20 +597,13 @@ StretchCalculator::findPeaks(const std::vector &rawDf) 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) { + m_log.log(2, "hard peak, three rises of 10%: chunk and df", i, df[i]); } } 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() && @@ -658,9 +611,7 @@ StretchCalculator::findPeaks(const std::vector &rawDf) ++peakLocation; - if (m_debugLevel > 1) { - std::cerr << "pushing hard peak forward to " << peakLocation << ": " << df[peakLocation] << " > " << df[peakLocation-1] << " * " << 1.4 << std::endl; - } + m_log.log(2, "big rise next, pushing hard peak forward to", peakLocation); } hardPeakCandidates.insert(peakLocation); @@ -671,14 +622,10 @@ StretchCalculator::findPeaks(const std::vector &rawDf) size_t medianmaxsize = lrint(ceil(double(m_sampleRate) / double(m_increment))); // 1 sec ish - if (m_debugLevel > 1) { - std::cerr << "mediansize = " << medianmaxsize << std::endl; - } + m_log.log(2, "mediansize", medianmaxsize); if (medianmaxsize < 7) { medianmaxsize = 7; - if (m_debugLevel > 1) { - std::cerr << "adjusted mediansize = " << medianmaxsize << std::endl; - } + m_log.log(2, "adjusted mediansize", medianmaxsize); } int minspacing = lrint(ceil(double(m_sampleRate) / @@ -722,10 +669,6 @@ StretchCalculator::findPeaks(const std::vector &rawDf) 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]); @@ -738,17 +681,6 @@ StretchCalculator::findPeaks(const std::vector &rawDf) 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] && @@ -768,26 +700,10 @@ StretchCalculator::findPeaks(const std::vector &rawDf) 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; - } - + m_log.log(2, "soft peak: chunk and median df", peak, medianwin[middle]); if (peak >= df.size()) { - if (m_debugLevel > 2) { - std::cerr << "peak is beyond end" << std::endl; - } + m_log.log(2, "peak is beyond end"); } else { softPeakCandidates.insert(peak); lastSoftPeak = peak; @@ -795,9 +711,7 @@ StretchCalculator::findPeaks(const std::vector &rawDf) } softPeakAmnesty = minspacing + maxindex - middle; - if (m_debugLevel > 2) { - std::cerr << "amnesty = " << softPeakAmnesty << std::endl; - } + m_log.log(3, "amnesty", softPeakAmnesty); } else if (softPeakAmnesty > 0) --softPeakAmnesty; @@ -829,26 +743,16 @@ StretchCalculator::findPeaks(const std::vector &rawDf) if (haveHardPeak && (!haveSoftPeak || hardPeak <= softPeak)) { - - if (m_debugLevel > 2) { - std::cerr << "Hard peak: " << hardPeak << std::endl; - } - + m_log.log(3, "hard peak", hardPeak); peak.hard = true; peak.chunk = hardPeak; hardPeakCandidates.erase(hardPeakCandidates.begin()); - } else { - if (m_debugLevel > 2) { - std::cerr << "Soft peak: " << softPeak << std::endl; - } + m_log.log(3, "soft peak", softPeak); 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; - } + m_log.log(3, "ignoring, as we just had a hard peak"); ignore = true; } } diff --git a/src/faster/R2Stretcher.cpp b/src/faster/R2Stretcher.cpp index 9cf21e9..6aa41a0 100644 --- a/src/faster/R2Stretcher.cpp +++ b/src/faster/R2Stretcher.cpp @@ -40,8 +40,6 @@ #include #include -using std::cerr; -using std::endl; using std::vector; using std::map; using std::set; @@ -112,9 +110,7 @@ R2Stretcher::R2Stretcher(size_t sampleRate, _initialised = true; } - if (m_debugLevel > 0) { - cerr << "R2Stretcher::R2Stretcher: rate = " << m_sampleRate << ", options = " << options << endl; - } + m_log.log(1, "R2Stretcher::R2Stretcher: rate, options", m_sampleRate, options); // Window size will vary according to the audio sample rate, but // we don't let it drop below the 48k default @@ -455,7 +451,7 @@ R2Stretcher::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; @@ -464,7 +460,8 @@ R2Stretcher::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); } } @@ -556,18 +553,19 @@ R2Stretcher::calculateSizes() #endif } - if (m_debugLevel > 0) { - cerr << "calculateSizes: outbuf size = " << m_outbufSize << endl; - } + m_log.log(1, "calculateSizes: outbuf size", m_outbufSize); } void 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; @@ -621,9 +619,8 @@ R2Stretcher::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) { @@ -713,17 +710,16 @@ R2Stretcher::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 R2Stretcher::reconfigure() { @@ -759,7 +755,7 @@ R2Stretcher::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 (HannWindow, m_aWindowSize); m_sincs[m_aWindowSize] = new SincWindow @@ -767,7 +763,7 @@ R2Stretcher::reconfigure() } 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 (HannWindow, m_sWindowSize); m_sincs[m_sWindowSize] = new SincWindow @@ -798,7 +794,7 @@ R2Stretcher::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; @@ -824,12 +820,10 @@ R2Stretcher::reconfigure() 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"); } } @@ -844,7 +838,7 @@ void R2Stretcher::setTransientsOption(RubberBandStretcher::Options options) { if (!m_realtime) { - cerr << "R2Stretcher::setTransientsOption: Not permissible in non-realtime mode" << endl; + m_log.log(0, "R2Stretcher::setTransientsOption: Not permissible in non-realtime mode"); return; } int mask = (RubberBandStretcher::OptionTransientsMixed | @@ -862,7 +856,7 @@ void R2Stretcher::setDetectorOption(RubberBandStretcher::Options options) { if (!m_realtime) { - cerr << "R2Stretcher::setDetectorOption: Not permissible in non-realtime mode" << endl; + m_log.log(0, "R2Stretcher::setDetectorOption: Not permissible in non-realtime mode"); return; } int mask = (RubberBandStretcher::OptionDetectorPercussive | @@ -911,7 +905,7 @@ void R2Stretcher::setPitchOption(RubberBandStretcher::Options options) { if (!m_realtime) { - cerr << "R2Stretcher::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; } @@ -933,14 +927,12 @@ R2Stretcher::study(const float *const *input, size_t samples, bool final) Profiler profiler("R2Stretcher::study"); if (m_realtime) { - if (m_debugLevel > 1) { - cerr << "R2Stretcher::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 << "R2Stretcher::study: Cannot study after processing" << endl; + m_log.log(0, "R2Stretcher::study: Cannot study after processing"); return; } m_mode = Studying; @@ -983,7 +975,8 @@ R2Stretcher::study(const float *const *input, size_t samples, bool final) 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; @@ -1044,8 +1037,8 @@ R2Stretcher::study(const float *const *input, size_t samples, bool final) 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); @@ -1059,7 +1052,6 @@ R2Stretcher::study(const float *const *input, size_t samples, bool final) // extra afterwards. m_inputDuration += m_increment; -// cerr << "incr input duration by increment: " << m_increment << " -> " << m_inputDuration << endl; inbuf.skip(m_increment); } } @@ -1067,8 +1059,6 @@ R2Stretcher::study(const float *const *input, size_t samples, bool final) 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; } @@ -1128,7 +1118,7 @@ R2Stretcher::calculateStretch() 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; } } @@ -1145,10 +1135,7 @@ R2Stretcher::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); } } @@ -1187,9 +1174,7 @@ R2Stretcher::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 @@ -1226,7 +1211,7 @@ R2Stretcher::process(const float *const *input, size_t samples, bool final) Profiler profiler("R2Stretcher::process"); if (m_mode == Finished) { - cerr << "R2Stretcher::process: Cannot process again after final chunk" << endl; + m_log.log(0, "R2Stretcher::process: Cannot process again after final chunk"); return; } @@ -1239,9 +1224,7 @@ R2Stretcher::process(const float *const *input, size_t samples, bool final) 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); @@ -1258,10 +1241,8 @@ R2Stretcher::process(const float *const *input, size_t samples, bool final) 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 @@ -1293,12 +1274,10 @@ R2Stretcher::process(const float *const *input, size_t samples, bool final) 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 @@ -1331,14 +1310,10 @@ R2Stretcher::process(const float *const *input, size_t samples, bool final) } #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/faster/StretcherChannelData.cpp b/src/faster/StretcherChannelData.cpp index 43af2d2..81c47d2 100644 --- a/src/faster/StretcherChannelData.cpp +++ b/src/faster/StretcherChannelData.cpp @@ -56,8 +56,6 @@ R2Stretcher::ChannelData::construct(const std::set &sizes, 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()) { @@ -68,8 +66,6 @@ R2Stretcher::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); @@ -117,8 +113,6 @@ void 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(); @@ -210,8 +204,6 @@ 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 diff --git a/src/faster/StretcherProcess.cpp b/src/faster/StretcherProcess.cpp index 64ebd07..455092c 100644 --- a/src/faster/StretcherProcess.cpp +++ b/src/faster/StretcherProcess.cpp @@ -40,9 +40,6 @@ using namespace RubberBand; -using std::cerr; -using std::endl; - namespace RubberBand { #ifndef NO_THREADING @@ -57,20 +54,13 @@ R2Stretcher::ProcessThread::ProcessThread(R2Stretcher *s, size_t c) : void 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); @@ -89,9 +79,7 @@ R2Stretcher::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; } } @@ -102,9 +90,7 @@ R2Stretcher::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 @@ -205,8 +191,7 @@ R2Stretcher::consumeChannel(size_t c, size_t reqSize = int(ceil(samples / m_pitchScale)); if (reqSize > cd.resamplebufSize) { - cerr << "WARNING: R2Stretcher::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); } @@ -286,9 +271,7 @@ R2Stretcher::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; } @@ -311,9 +294,7 @@ R2Stretcher::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); @@ -330,9 +311,8 @@ R2Stretcher::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); @@ -351,9 +331,7 @@ R2Stretcher::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]; @@ -405,11 +383,7 @@ R2Stretcher::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 } @@ -418,18 +392,10 @@ R2Stretcher::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; } } @@ -450,9 +416,9 @@ R2Stretcher::processChunkForChannel(size_t c, // 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]; @@ -487,19 +453,13 @@ R2Stretcher::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; } @@ -513,9 +473,7 @@ R2Stretcher::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 @@ -528,12 +486,10 @@ R2Stretcher::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); } @@ -548,8 +504,6 @@ R2Stretcher::calculateIncrements(size_t &phaseIncrementRtn, { 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 @@ -572,7 +526,7 @@ R2Stretcher::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: R2Stretcher::calculateIncrements: Channels are not in sync" << endl; + m_log.log(0, "ERROR: R2Stretcher::calculateIncrements: Channels are not in sync"); return; } } @@ -669,10 +623,7 @@ R2Stretcher::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); } } @@ -711,9 +662,6 @@ R2Stretcher::getIncrements(size_t channel, bool gotData = true; if (cd.chunkCount >= m_outputIncrements.size()) { -// cerr << "WARNING: R2Stretcher::getIncrements:" -// << " chunk count " << cd.chunkCount << " >= " -// << m_outputIncrements.size() << endl; if (m_outputIncrements.size() == 0) { phaseIncrementRtn = m_increment; shiftIncrementRtn = m_increment; @@ -740,12 +688,12 @@ R2Stretcher::getIncrements(size_t channel, if (shiftIncrement < 0) { shiftIncrement = -shiftIncrement; } - /* - if (shiftIncrement >= int(m_windowSize)) { - cerr << "*** ERROR: R2Stretcher::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 @@ -782,8 +730,8 @@ R2Stretcher::modifyChunk(size_t channel, 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); @@ -904,15 +852,13 @@ R2Stretcher::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); } } @@ -936,8 +882,6 @@ R2Stretcher::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; @@ -1061,8 +1005,10 @@ R2Stretcher::writeChunk(size_t channel, size_t shiftIncrement, bool last) 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); @@ -1090,12 +1036,10 @@ R2Stretcher::writeChunk(size_t channel, size_t shiftIncrement, bool last) // 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: R2Stretcher::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); } - #if defined(STRETCHER_IMPL_RESAMPLER_MUTEX_REQUIRED) if (m_threaded) { m_resamplerMutex.lock(); @@ -1134,9 +1078,7 @@ R2Stretcher::writeChunk(size_t channel, size_t shiftIncrement, bool last) } else { cd.accumulatorFill = 0; if (cd.draining) { - if (m_debugLevel > 1) { - cerr << "R2Stretcher::processChunks: setting outputComplete to true" << endl; - } + m_log.log(2, "processChunks: setting outputComplete to true"); cd.outputComplete = true; } } @@ -1162,31 +1104,23 @@ R2Stretcher::writeOutput(RingBuffer &to, float *from, size_t qty, size_t // 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: R2Stretcher::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; @@ -1196,22 +1130,16 @@ R2Stretcher::writeOutput(RingBuffer &to, float *from, size_t qty, size_t // 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; } @@ -1235,11 +1163,8 @@ R2Stretcher::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; @@ -1258,9 +1183,7 @@ R2Stretcher::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 +1207,7 @@ R2Stretcher::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 << "R2Stretcher::retrieve: WARNING: channel imbalance detected" << endl; - } + m_log.log(0, "R2Stretcher::retrieve: WARNING: channel imbalance detected"); } got = gotHere; } diff --git a/src/finer/R3Stretcher.cpp b/src/finer/R3Stretcher.cpp index 67bcaab..32ba055 100644 --- a/src/finer/R3Stretcher.cpp +++ b/src/finer/R3Stretcher.cpp @@ -134,15 +134,15 @@ R3Stretcher::R3Stretcher(Parameters parameters, // changes. if (!isRealTime()) { - m_log.log(1, "Offline mode: pre-padding"); 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); } // By the time we skip this later we will have resampled m_startSkip = int(round(pad / m_pitchScale)); } else { - m_log.log(1, "RT mode: no internal pre-pad"); + m_log.log(1, "realtime mode: no prefill"); } } From b318fb4e8b28a293a29e2b8140ad563b02137b71 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 22 Jun 2022 13:42:58 +0100 Subject: [PATCH 141/184] Wire through debug level changes; tidy --- src/RubberBandStretcher.cpp | 1 - src/faster/R2Stretcher.cpp | 25 ++++++++++++++----------- src/faster/R2Stretcher.h | 3 --- src/faster/StretcherProcess.cpp | 2 +- src/finer/Guide.h | 11 ++++++++++- src/finer/PhaseAdvance.h | 22 +++++++++++----------- src/finer/R3Stretcher.cpp | 5 +++++ src/finer/R3Stretcher.h | 7 ++++++- 8 files changed, 47 insertions(+), 29 deletions(-) diff --git a/src/RubberBandStretcher.cpp b/src/RubberBandStretcher.cpp index d183ecf..493f40c 100644 --- a/src/RubberBandStretcher.cpp +++ b/src/RubberBandStretcher.cpp @@ -300,7 +300,6 @@ public: setDefaultDebugLevel(int level) { Log::setDefaultDebugLevel(level); - R2Stretcher::setDefaultDebugLevel(level); //!!! } }; diff --git a/src/faster/R2Stretcher.cpp b/src/faster/R2Stretcher.cpp index 6aa41a0..46e044c 100644 --- a/src/faster/R2Stretcher.cpp +++ b/src/faster/R2Stretcher.cpp @@ -54,9 +54,6 @@ R2Stretcher::m_defaultIncrement = 256; const size_t R2Stretcher::m_defaultFftSize = 2048; -int -R2Stretcher::m_defaultDebugLevel = 0; - static bool _initialised = false; R2Stretcher::R2Stretcher(size_t sampleRate, @@ -82,7 +79,6 @@ R2Stretcher::R2Stretcher(size_t sampleRate, m_realtime(false), m_options(options), m_log(log), - m_debugLevel(m_defaultDebugLevel), m_mode(JustCreated), m_awindow(0), m_afilter(0), @@ -110,7 +106,10 @@ R2Stretcher::R2Stretcher(size_t sampleRate, _initialised = true; } - m_log.log(1, "R2Stretcher::R2Stretcher: rate, options", m_sampleRate, options); + 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 @@ -641,7 +640,7 @@ R2Stretcher::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(); } @@ -666,7 +665,8 @@ R2Stretcher::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); @@ -695,7 +695,7 @@ R2Stretcher::configure() !(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 @@ -801,7 +801,8 @@ R2Stretcher::reconfigure() 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); @@ -1152,8 +1153,10 @@ R2Stretcher::calculateStretch() void 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 diff --git a/src/faster/R2Stretcher.h b/src/faster/R2Stretcher.h index c3dca37..b9e1709 100644 --- a/src/faster/R2Stretcher.h +++ b/src/faster/R2Stretcher.h @@ -100,7 +100,6 @@ public: void calculateStretch(); void setDebugLevel(int level); - static void setDefaultDebugLevel(int level) { m_defaultDebugLevel = level; } protected: size_t m_sampleRate; @@ -177,7 +176,6 @@ protected: bool m_realtime; RubberBandStretcher::Options m_options; Log m_log; - int m_debugLevel; //!!! to go once Log switch complete enum ProcessMode { JustCreated, @@ -255,7 +253,6 @@ protected: void writeOutput(RingBuffer &to, float *from, size_t qty, size_t &outCount, size_t theoreticalOut); - static int m_defaultDebugLevel; //!!! to go static const size_t m_defaultIncrement; static const size_t m_defaultFftSize; }; diff --git a/src/faster/StretcherProcess.cpp b/src/faster/StretcherProcess.cpp index 455092c..7044c7a 100644 --- a/src/faster/StretcherProcess.cpp +++ b/src/faster/StretcherProcess.cpp @@ -441,7 +441,7 @@ R2Stretcher::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; diff --git a/src/finer/Guide.h b/src/finer/Guide.h index 8ac8ecc..54d2599 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -119,10 +119,12 @@ public: { 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 @@ -133,6 +135,9 @@ public: 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 { @@ -331,6 +336,10 @@ public: */ } + void setDebugLevel(int level) { + m_log.setDebugLevel(level); + } + protected: Parameters m_parameters; Log m_log; diff --git a/src/finer/PhaseAdvance.h b/src/finer/PhaseAdvance.h index 11baf98..b2dd33c 100644 --- a/src/finer/PhaseAdvance.h +++ b/src/finer/PhaseAdvance.h @@ -111,17 +111,17 @@ public: int highest = configuration.fftBandLimits[myFftBand].b1max; if (m_log.getDebugLevel() > 0 && !m_reported) { - std::ostringstream ostr; - ostr << "PhaseAdvance: fftSize = " << m_parameters.fftSize - << ": bins = " << bs << ", channels = " << channels - << ", inhop = "<< inhop << ", outhop = " << outhop - << ", ratio = " << ratio << std::endl; - ostr << "PhaseAdvance: lowest possible bin = " << lowest - << " (" << configuration.fftBandLimits[myFftBand].f0min - << "Hz), highest = " << highest - << " (" << configuration.fftBandLimits[myFftBand].f1max - << "Hz)" << std::endl; - m_log.log(1, ostr.str().c_str()); + 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; } diff --git a/src/finer/R3Stretcher.cpp b/src/finer/R3Stretcher.cpp index 32ba055..1fe3bb7 100644 --- a/src/finer/R3Stretcher.cpp +++ b/src/finer/R3Stretcher.cpp @@ -53,6 +53,11 @@ R3Stretcher::R3Stretcher(Parameters parameters, 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; diff --git a/src/finer/R3Stretcher.h b/src/finer/R3Stretcher.h index 292f2c0..161c2b0 100644 --- a/src/finer/R3Stretcher.h +++ b/src/finer/R3Stretcher.h @@ -88,7 +88,12 @@ public: size_t getChannelCount() const; void setDebugLevel(int level) { - m_log.setDebugLevel(level); //!!! +others + m_log.setDebugLevel(level); + for (auto &sd : m_scaleData) { + sd.second->guided.setDebugLevel(level); + } + m_guide.setDebugLevel(level); + m_calculator->setDebugLevel(level); } protected: From 9845e4bb388a4864b74033de33d95737a5a5a023 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 23 Jun 2022 09:52:23 +0100 Subject: [PATCH 142/184] Make this a default implementation of Logger rather than a special case in Log --- rubberband/RubberBandStretcher.h | 4 ++-- src/RubberBandStretcher.cpp | 25 ++++++++++++++++++++++++- src/common/Log.cpp | 23 ----------------------- src/common/Log.h | 19 ++++--------------- vamp/RubberBandVampPlugin.cpp | 25 +++++++++++++++++++++++-- 5 files changed, 53 insertions(+), 43 deletions(-) diff --git a/rubberband/RubberBandStretcher.h b/rubberband/RubberBandStretcher.h index 4be1bd0..b806e48 100644 --- a/rubberband/RubberBandStretcher.h +++ b/rubberband/RubberBandStretcher.h @@ -383,8 +383,8 @@ public: * 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 generated and sent to the - * logger in the first place. + * whether any given debug message is sent to the logger in the + * first place. */ RubberBandStretcher(size_t sampleRate, size_t channels, diff --git a/src/RubberBandStretcher.cpp b/src/RubberBandStretcher.cpp index 493f40c..2202832 100644 --- a/src/RubberBandStretcher.cpp +++ b/src/RubberBandStretcher.cpp @@ -24,6 +24,8 @@ #include "faster/R2Stretcher.h" #include "finer/R3Stretcher.h" +#include + namespace RubberBand { class RubberBandStretcher::Impl @@ -31,6 +33,26 @@ 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( @@ -45,7 +67,8 @@ class RubberBandStretcher::Impl } ); } else { - return Log::makeCoutLog(); + return makeRBLog(std::shared_ptr + (new CerrLogger())); } } diff --git a/src/common/Log.cpp b/src/common/Log.cpp index c2c944c..03b3536 100644 --- a/src/common/Log.cpp +++ b/src/common/Log.cpp @@ -28,27 +28,4 @@ namespace RubberBand int Log::m_defaultDebugLevel = 0; -Log -Log::makeCoutLog() -{ - return Log( - [](const char *message) { - std::cout << "RubberBand: " << message << "\n"; - }, - [](const char *message, double arg0) { - auto prec = std::cout.precision(); - std::cout.precision(10); - std::cout << "RubberBand: " << message << ": " << arg0 << "\n"; - std::cout.precision(prec); - }, - [](const char *message, double arg0, double arg1) { - auto prec = std::cout.precision(); - std::cout.precision(10); - std::cout << "RubberBand: " << message - << ": (" << arg0 << ", " << arg1 << ")" << "\n"; - std::cout.precision(prec); - } - ); -} - } diff --git a/src/common/Log.h b/src/common/Log.h index 1a9038c..b78315e 100644 --- a/src/common/Log.h +++ b/src/common/Log.h @@ -39,19 +39,10 @@ public: m_log2(_log2), m_debugLevel(m_defaultDebugLevel) { } - Log(const Log &other) : - m_log0(other.m_log0), - m_log1(other.m_log1), - m_log2(other.m_log2), - m_debugLevel(other.m_debugLevel) { } - - Log &operator=(const Log &other) { - m_log0 = other.m_log0; - m_log1 = other.m_log1; - m_log2 = other.m_log2; - m_debugLevel = other.m_debugLevel; - return *this; - } + 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; } @@ -68,8 +59,6 @@ public: if (level <= m_debugLevel) m_log2(message, arg0, arg1); } - static Log makeCoutLog(); - private: std::function m_log0; std::function m_log1; diff --git a/vamp/RubberBandVampPlugin.cpp b/vamp/RubberBandVampPlugin.cpp index a00ee1c..028ee81 100644 --- a/vamp/RubberBandVampPlugin.cpp +++ b/vamp/RubberBandVampPlugin.cpp @@ -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() { @@ -464,8 +486,7 @@ RubberBandVampPlugin::Impl::getRemainingFeaturesOffline() int rate = m_sampleRate; RubberBand::StretchCalculator sc - (rate, m_stretcher->getInputIncrement(), true, - RubberBand::Log::makeCoutLog()); + (rate, m_stretcher->getInputIncrement(), true, makeCerrLog()); size_t inputIncrement = m_stretcher->getInputIncrement(); std::vector outputIncrements = m_stretcher->getOutputIncrements(); From 539c35c4ee40c1854bb1fedf949fe686e30fdaed Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 23 Jun 2022 11:53:35 +0100 Subject: [PATCH 143/184] Handful of StretchCalculator tests --- meson.build | 1 + src/test/TestStretchCalculator.cpp | 126 +++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 src/test/TestStretchCalculator.cpp diff --git a/meson.build b/meson.build index 6aa8677..8345d48 100644 --- a/meson.build +++ b/meson.build @@ -92,6 +92,7 @@ unit_test_sources = [ 'src/test/TestVectorOpsComplex.cpp', 'src/test/TestVectorOps.cpp', 'src/test/TestSignalBits.cpp', + 'src/test/TestStretchCalculator.cpp', 'src/test/TestBinClassifier.cpp', 'src/test/test.cpp', ] diff --git a/src/test/TestStretchCalculator.cpp b/src/test/TestStretchCalculator.cpp new file mode 100644 index 0000000..ffe7d44 --- /dev/null +++ b/src/test/TestStretchCalculator.cpp @@ -0,0 +1,126 @@ +/* -*- 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. +*/ + +#define BOOST_TEST_DYN_LINK +#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() + From 3b168ca55eeafe8be5029381c7c0b7029935fe75 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 23 Jun 2022 15:13:48 +0100 Subject: [PATCH 144/184] Initial simple test --- meson.build | 1 + src/test/TestStretcher.cpp | 88 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 src/test/TestStretcher.cpp diff --git a/meson.build b/meson.build index 8345d48..28786fc 100644 --- a/meson.build +++ b/meson.build @@ -93,6 +93,7 @@ unit_test_sources = [ 'src/test/TestVectorOps.cpp', 'src/test/TestSignalBits.cpp', 'src/test/TestStretchCalculator.cpp', + 'src/test/TestStretcher.cpp', 'src/test/TestBinClassifier.cpp', 'src/test/test.cpp', ] diff --git a/src/test/TestStretcher.cpp b/src/test/TestStretcher.cpp new file mode 100644 index 0000000..db29c66 --- /dev/null +++ b/src/test/TestStretcher.cpp @@ -0,0 +1,88 @@ +/* -*- 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. +*/ + +#define BOOST_TEST_DYN_LINK +#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(sinusoid_unchanged_single_offline) +{ + int n = 10000; + float freq = 440.f; + int rate = 44100; + RubberBandStretcher stretcher(rate, 1); + 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); + + 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. + + // 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(in == out, + tt::tolerance(0.1f) << tt::per_element()); + + BOOST_TEST(vector(in.begin() + 1000, in.begin() + n - 1000) == + vector(out.begin() + 1000, out.begin() + n - 1000), + tt::tolerance(0.0001f) << tt::per_element()); +} + +BOOST_AUTO_TEST_SUITE_END() From d65755427f73aeeb4fa3693d0a2959ed181930a1 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 24 Jun 2022 10:51:40 +0100 Subject: [PATCH 145/184] In offline mode, create the resampler only if needed (i.e. if the pitch ratio is still 1.0 at the point when process is first called); and use plain 2048-sample fft with unity stretch --- src/finer/Guide.h | 25 ++++++++-- src/finer/R3Stretcher.cpp | 100 +++++++++++++++++++++++--------------- src/finer/R3Stretcher.h | 1 + 3 files changed, 84 insertions(+), 42 deletions(-) diff --git a/src/finer/Guide.h b/src/finer/Guide.h index 54d2599..bfb48a5 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -127,7 +127,8 @@ public: // 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 + // 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); @@ -154,6 +155,7 @@ public: const BinSegmenter::Segmentation &nextSegmentation, double meanMagnitude, int unityCount, + bool realtime, Guidance &guidance) const { bool hadPhaseReset = guidance.phaseReset.present; @@ -182,7 +184,8 @@ public: hadPhaseReset, unityCount, magnitudes, - segmentation); + segmentation, + realtime); return; } @@ -380,12 +383,28 @@ protected: bool hadPhaseReset, uint32_t unityCount, const double *const magnitudes, - const BinSegmenter::Segmentation &segmentation) const { + 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; diff --git a/src/finer/R3Stretcher.cpp b/src/finer/R3Stretcher.cpp index 1fe3bb7..6441c4c 100644 --- a/src/finer/R3Stretcher.cpp +++ b/src/finer/R3Stretcher.cpp @@ -104,22 +104,13 @@ R3Stretcher::R3Stretcher(Parameters parameters, 1, false, // no fixed inputIncrement m_log)); - Resampler::Parameters resamplerParameters; - resamplerParameters.quality = Resampler::FastestTolerable; - 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; + 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 } - - resamplerParameters.initialSampleRate = m_parameters.sampleRate; - resamplerParameters.maxBufferSize = m_guideConfiguration.longestFftSize; //!!!??? - m_resampler = std::unique_ptr - (new Resampler(resamplerParameters, m_parameters.channels)); calculateHop(); @@ -132,23 +123,6 @@ R3Stretcher::R3Stretcher(Parameters parameters, if (!m_timeRatio.is_lock_free()) { m_log.log(0, "WARNING: std::atomic is not lock-free"); } - - // Pad to half of 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. - - if (!isRealTime()) { - 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); - } - // By the time we skip this later we will have resampled - m_startSkip = int(round(pad / m_pitchScale)); - } else { - m_log.log(1, "realtime mode: no prefill"); - } } WindowType @@ -249,6 +223,27 @@ R3Stretcher::setKeyFrameMap(const std::map &mapping) 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() { @@ -398,7 +393,9 @@ void R3Stretcher::reset() { m_calculator->reset(); - m_resampler->reset(); + if (m_resampler) { + m_resampler->reset(); + } for (auto &it : m_scaleData) { it.second->guided.reset(); @@ -463,12 +460,36 @@ R3Stretcher::process(const float *const *input, size_t samples, bool final) return; } - if (!isRealTime() && !m_keyFrameMap.empty()) { - if (m_mode == ProcessMode::Studying) { - m_totalTargetDuration = - size_t(round(m_studyInputDuration * getEffectiveRatio())); + if (!isRealTime()) { + + if (m_mode == ProcessMode::JustCreated || + m_mode == ProcessMode::Studying) { + + if (m_pitchScale != 1.0 && !m_resampler) { + createResampler(); + } + + // Pad to half of 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); + } + // By the time we skip this later we will have resampled + m_startSkip = int(round(pad / m_pitchScale)); + } + + if (!m_keyFrameMap.empty()) { + if (m_mode == ProcessMode::Studying) { + m_totalTargetDuration = + size_t(round(m_studyInputDuration * getEffectiveRatio())); + } + updateRatioFromMap(); } - updateRatioFromMap(); } if (final) { @@ -891,7 +912,7 @@ R3Stretcher::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) double ratio = getEffectiveRatio(); - if (fabs(ratio - 1.0) < 1.0e-6) { + if (fabs(ratio - 1.0) < 1.0e-7) { ++m_unityCount; } else { m_unityCount = 0; @@ -907,6 +928,7 @@ R3Stretcher::analyseChannel(int c, int inhop, int prevInhop, int prevOuthop) cd->nextSegmentation, v_mean(classifyScale->mag.data() + 1, classify/2), m_unityCount, + isRealTime(), cd->guidance); /* if (c == 0) { @@ -1070,7 +1092,7 @@ R3Stretcher::synthesiseChannel(int c, int outhop) 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 diff --git a/src/finer/R3Stretcher.h b/src/finer/R3Stretcher.h index 161c2b0..3f9cd4f 100644 --- a/src/finer/R3Stretcher.h +++ b/src/finer/R3Stretcher.h @@ -310,6 +310,7 @@ protected: ProcessMode m_mode; void consume(); + void createResampler(); void calculateHop(); void updateRatioFromMap(); void analyseChannel(int channel, int inhop, int prevInhop, int prevOuthop); From 2478d54c05278a98cf6c2961d637a63cde41293a Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 24 Jun 2022 11:51:25 +0100 Subject: [PATCH 146/184] We actually need to fill with the whole size (matching our reported latency) --- src/finer/R3Stretcher.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/finer/R3Stretcher.cpp b/src/finer/R3Stretcher.cpp index 6441c4c..00ab1a0 100644 --- a/src/finer/R3Stretcher.cpp +++ b/src/finer/R3Stretcher.cpp @@ -474,7 +474,7 @@ R3Stretcher::process(const float *const *input, size_t samples, bool final) // with a swoosh than introduce more latency, and we don't // want gaps when the ratio changes. - int pad = m_guideConfiguration.longestFftSize / 2; + int pad = m_guideConfiguration.longestFftSize; m_log.log(1, "offline mode: prefilling with", pad); for (int c = 0; c < m_parameters.channels; ++c) { m_channelData[c]->inbuf->zero(pad); @@ -556,10 +556,6 @@ R3Stretcher::consume() { int longest = m_guideConfiguration.longestFftSize; int channels = m_parameters.channels; - - //!!! todo: wire debug level & logger throughout -// m_calculator->setDebugLevel(3); - int inhop = m_inhop; double effectivePitchRatio = 1.0 / m_pitchScale; From 9c04885d270249706ffb6699eb438a28907b33a2 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 24 Jun 2022 13:25:36 +0100 Subject: [PATCH 147/184] Read correct sample count out from mixdown buffer, when draining and having read less than one outhop at input --- src/finer/R3Stretcher.cpp | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/finer/R3Stretcher.cpp b/src/finer/R3Stretcher.cpp index 00ab1a0..dad5290 100644 --- a/src/finer/R3Stretcher.cpp +++ b/src/finer/R3Stretcher.cpp @@ -487,6 +487,7 @@ R3Stretcher::process(const float *const *input, size_t samples, bool final) if (m_mode == ProcessMode::Studying) { m_totalTargetDuration = size_t(round(m_studyInputDuration * getEffectiveRatio())); + m_log.log(1, "for keyframe map: study duration and target duration", m_studyInputDuration, m_totalTargetDuration); } updateRatioFromMap(); } @@ -560,7 +561,8 @@ R3Stretcher::consume() double effectivePitchRatio = 1.0 / m_pitchScale; if (m_resampler) { - effectivePitchRatio = m_resampler->getEffectiveRatio(effectivePitchRatio); + effectivePitchRatio = + m_resampler->getEffectiveRatio(effectivePitchRatio); } int outhop = m_calculator->calculateSingle(m_timeRatio, @@ -650,6 +652,16 @@ R3Stretcher::consume() 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; @@ -663,9 +675,9 @@ R3Stretcher::consume() (m_channelAssembly.resampled.data(), m_channelData[0]->resampled.size(), m_channelAssembly.mixdown.data(), - outhop, + validCount, 1.0 / m_pitchScale, - m_mode == ProcessMode::Finished && readSpace < longest); + m_mode == ProcessMode::Finished && readSpace < inhop); } // Emit @@ -674,21 +686,27 @@ R3Stretcher::consume() auto &cd = m_channelData.at(c); if (m_resampler) { cd->outbuf->write(cd->resampled.data(), resampledCount); - if (c == 0) m_totalOutputDuration += resampledCount; } else { - cd->outbuf->write(cd->mixdown.data(), outhop); - if (c == 0) m_totalOutputDuration += outhop; + cd->outbuf->write(cd->mixdown.data(), validCount); } - int readSpace = cd->inbuf->getReadSpace(); 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); } } + if (m_resampler) { + m_totalOutputDuration += resampledCount; + } else { + m_totalOutputDuration += validCount; + } + if (m_startSkip > 0) { int toSkip = std::min (m_startSkip, m_channelData.at(0)->outbuf->getReadSpace()); From b9e6ebb6e32180050e899d7d2eb205caffd0f16b Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 24 Jun 2022 14:01:11 +0100 Subject: [PATCH 148/184] Implement setMaxProcessSize in R3 --- src/RubberBandStretcher.cpp | 3 ++- src/finer/R3Stretcher.cpp | 20 ++++++++++++++++++-- src/finer/R3Stretcher.h | 2 ++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/RubberBandStretcher.cpp b/src/RubberBandStretcher.cpp index 2202832..575ae53 100644 --- a/src/RubberBandStretcher.cpp +++ b/src/RubberBandStretcher.cpp @@ -206,7 +206,8 @@ public: void setMaxProcessSize(size_t samples) { - if (m_r2) m_r2->setMaxProcessSize(samples); //!!! definitely need for r3d + if (m_r2) m_r2->setMaxProcessSize(samples); + else m_r3->setMaxProcessSize(samples); } void diff --git a/src/finer/R3Stretcher.cpp b/src/finer/R3Stretcher.cpp index dad5290..5bf0e1d 100644 --- a/src/finer/R3Stretcher.cpp +++ b/src/finer/R3Stretcher.cpp @@ -452,6 +452,23 @@ R3Stretcher::getSamplesRequired() const } } +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) { @@ -505,8 +522,7 @@ R3Stretcher::process(const float *const *input, size_t samples, bool final) size_t ws = m_channelData[0]->inbuf->getWriteSpace(); if (samples > ws) { - //!!! check this - 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."); + 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); diff --git a/src/finer/R3Stretcher.h b/src/finer/R3Stretcher.h index 3f9cd4f..3625518 100644 --- a/src/finer/R3Stretcher.h +++ b/src/finer/R3Stretcher.h @@ -86,6 +86,8 @@ public: size_t getLatency() const; size_t getChannelCount() const; + + void setMaxProcessSize(size_t samples); void setDebugLevel(int level) { m_log.setDebugLevel(level); From 512d85504338b448c165d9494618817155ad8f77 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 24 Jun 2022 14:01:22 +0100 Subject: [PATCH 149/184] An R3 test --- src/test/TestStretcher.cpp | 67 ++++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/src/test/TestStretcher.cpp b/src/test/TestStretcher.cpp index db29c66..5f2355f 100644 --- a/src/test/TestStretcher.cpp +++ b/src/test/TestStretcher.cpp @@ -36,12 +36,14 @@ namespace tt = boost::test_tools; BOOST_AUTO_TEST_SUITE(TestStretcher) -BOOST_AUTO_TEST_CASE(sinusoid_unchanged_single_offline) +BOOST_AUTO_TEST_CASE(sinusoid_unchanged_single_offline_faster) { int n = 10000; float freq = 440.f; int rate = 44100; - RubberBandStretcher stretcher(rate, 1); + 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)); @@ -58,6 +60,8 @@ BOOST_AUTO_TEST_CASE(sinusoid_unchanged_single_offline) 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); @@ -66,7 +70,10 @@ BOOST_AUTO_TEST_CASE(sinusoid_unchanged_single_offline) // 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. + // threshold of 0.1; in the middle we expect better + // precision. Note that these are relative precisions, not + // absolute, i.e. 0.001 means 0.001 of 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 @@ -80,9 +87,57 @@ BOOST_AUTO_TEST_CASE(sinusoid_unchanged_single_offline) BOOST_TEST(in == out, tt::tolerance(0.1f) << tt::per_element()); - BOOST_TEST(vector(in.begin() + 1000, in.begin() + n - 1000) == - vector(out.begin() + 1000, out.begin() + n - 1000), - tt::tolerance(0.0001f) << tt::per_element()); + BOOST_TEST(vector(in.begin() + 1024, in.begin() + n - 1024) == + vector(out.begin() + 1024, out.begin() + n - 1024), + tt::tolerance(0.001f) << tt::per_element()); +} + +BOOST_AUTO_TEST_CASE(sinusoid_unchanged_single_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(in == out, + tt::tolerance(0.15f) << tt::per_element()); + + BOOST_TEST(vector(in.begin() + 1024, in.begin() + n - 1024) == + vector(out.begin() + 1024, out.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_SUITE_END() From 5e726e79aa4eb6fe25cf566f4cde303e68dd247b Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 24 Jun 2022 18:05:32 +0100 Subject: [PATCH 150/184] Toward further tests --- src/finer/R3Stretcher.cpp | 4 +- src/test/TestStretcher.cpp | 89 ++++++++++++++++++++++++++++++++++---- 2 files changed, 83 insertions(+), 10 deletions(-) diff --git a/src/finer/R3Stretcher.cpp b/src/finer/R3Stretcher.cpp index 5bf0e1d..79332ad 100644 --- a/src/finer/R3Stretcher.cpp +++ b/src/finer/R3Stretcher.cpp @@ -378,8 +378,8 @@ R3Stretcher::getLatency() const if (!isRealTime()) { return 0; } else { - double factor = m_pitchScale * 0.5; - return size_t(ceil(m_guideConfiguration.longestFftSize * factor)); + return size_t(ceil(m_guideConfiguration.longestFftSize + * 0.5 * m_pitchScale)); } } diff --git a/src/test/TestStretcher.cpp b/src/test/TestStretcher.cpp index 5f2355f..4d5f26e 100644 --- a/src/test/TestStretcher.cpp +++ b/src/test/TestStretcher.cpp @@ -71,8 +71,8 @@ BOOST_AUTO_TEST_CASE(sinusoid_unchanged_single_offline_faster) // 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 precisions, not - // absolute, i.e. 0.001 means 0.001 of the smaller value - so they + // 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 @@ -84,11 +84,11 @@ BOOST_AUTO_TEST_CASE(sinusoid_unchanged_single_offline_faster) // you're comparing are floats (it sets the tolerance for doubles, // leaving float comparison unchanged). Clever... too clever. - BOOST_TEST(in == out, + BOOST_TEST(out == in, tt::tolerance(0.1f) << tt::per_element()); - BOOST_TEST(vector(in.begin() + 1024, in.begin() + n - 1024) == - vector(out.begin() + 1024, out.begin() + n - 1024), + 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()); } @@ -127,11 +127,11 @@ BOOST_AUTO_TEST_CASE(sinusoid_unchanged_single_offline_finer) // its different windowing design, though see the note above about // what these tolerances mean - BOOST_TEST(in == out, + BOOST_TEST(out == in, tt::tolerance(0.15f) << tt::per_element()); - BOOST_TEST(vector(in.begin() + 1024, in.begin() + n - 1024) == - vector(out.begin() + 1024, out.begin() + n - 1024), + 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; @@ -140,4 +140,77 @@ BOOST_AUTO_TEST_CASE(sinusoid_unchanged_single_offline_finer) // } } +#ifdef NOT_YET + +BOOST_AUTO_TEST_CASE(impulses_2_offline_faster) +{ + int n = 10000; + float freq = 440.f; + int rate = 44100; + RubberBandStretcher stretcher + (rate, 1, RubberBandStretcher::OptionEngineFaster, 2.0, 1.0); + + vector in(n, 0.f), out(n * 2, 0.f); + + in[0] = 1.f; + in[1] = -1.f; + + in[4999] = 1.f; + in[5000] = -1.f; + + in[9998] = 1.f; + in[9999] = -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); + + float max; + int peak0, peak1, peak2; + + for (int i = 0, max = -2.f; i < n/2; ++i) { + if (out[i] > max) { + max = out[i]; + peak0 = i; + } + } + for (int i = n/2, max = -2.f; i < (n*3)/2; ++i) { + if (out[i] > max) { + max = out[i]; + peak1 = i; + } + } + for (int i = (n*3)/2, max = -2.f; i < n*2; ++i) { + if (out[i] > max) { + max = out[i]; + peak2 = i; + } + } + + BOOST_TEST(peak0 == 0); + BOOST_TEST(peak1 == n - 1); + BOOST_TEST(peak2 == n*2 - 2); + + std::cout << "ms\tV" << std::endl; + for (int i = 0; i < n*2; ++i) { + std::cout << i << "\t" << out[i] << std::endl; + } +} + +#endif + BOOST_AUTO_TEST_SUITE_END() From 2940bde16f380225f292dcbd467259484c4efacb Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 27 Jun 2022 16:37:52 +0100 Subject: [PATCH 151/184] Since we're adding more padding than a half-frame, we have to remove a scaled proportion of it --- src/finer/R3Stretcher.cpp | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/finer/R3Stretcher.cpp b/src/finer/R3Stretcher.cpp index 79332ad..b204990 100644 --- a/src/finer/R3Stretcher.cpp +++ b/src/finer/R3Stretcher.cpp @@ -486,18 +486,28 @@ R3Stretcher::process(const float *const *input, size_t samples, bool final) createResampler(); } - // Pad to half of 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; + // Pad to 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 half = m_guideConfiguration.longestFftSize / 2; + int pad = half + half; m_log.log(1, "offline mode: prefilling with", pad); for (int c = 0; c < m_parameters.channels; ++c) { m_channelData[c]->inbuf->zero(pad); } - // By the time we skip this later we will have resampled - m_startSkip = int(round(pad / m_pitchScale)); + + // NB by the time we skip this later we may have resampled + // as well as stretched + + m_startSkip = half + int(round(half / m_pitchScale * m_timeRatio)); + 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 (!m_keyFrameMap.empty()) { From 0452145ff307a41cd4422db6a2779a4359c2e28c Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 28 Jun 2022 11:18:58 +0100 Subject: [PATCH 152/184] Avoid compiler warning --- src/finer/Guide.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/finer/Guide.h b/src/finer/Guide.h index bfb48a5..a7e5538 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -381,8 +381,8 @@ protected: void updateForUnity(Guidance &guidance, bool hadPhaseReset, - uint32_t unityCount, - const double *const magnitudes, + uint32_t /* unityCount */, + const double *const /* magnitudes */, const BinSegmenter::Segmentation &segmentation, bool realtime) const { From a7f9c47a00690b4bed470f581ca5cd823c0952fb Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 28 Jun 2022 11:47:30 +0100 Subject: [PATCH 153/184] Document engine; add getEngineVersion() --- rubberband/RubberBandStretcher.h | 43 +++++++++++++++++ src/RubberBandStretcher.cpp | 12 +++++ src/test/TestStretcher.cpp | 81 +++++++++++++++++++++++++++++++- 3 files changed, 135 insertions(+), 1 deletion(-) diff --git a/rubberband/RubberBandStretcher.h b/rubberband/RubberBandStretcher.h index b806e48..ebd894b 100644 --- a/rubberband/RubberBandStretcher.h +++ b/rubberband/RubberBandStretcher.h @@ -111,6 +111,36 @@ public: * non-real-time operation on seekable files: Offline; real-time * or streaming operation: RealTime. * + * 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 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 for + * backward compatibility. 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 added 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 @@ -402,6 +432,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 @@ -477,6 +516,8 @@ public: * * 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); @@ -499,6 +540,8 @@ public: * * 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; diff --git a/src/RubberBandStretcher.cpp b/src/RubberBandStretcher.cpp index 575ae53..c17e3ba 100644 --- a/src/RubberBandStretcher.cpp +++ b/src/RubberBandStretcher.cpp @@ -96,6 +96,12 @@ public: delete m_r3; } + int getEngineVersion() const + { + if (m_r3) return 3; + else return 2; + } + void reset() { if (m_r2) m_r2->reset(); @@ -359,6 +365,12 @@ RubberBandStretcher::reset() m_d->reset(); } +int +RubberBandStretcher::getEngineVersion() const +{ + return m_d->getEngineVersion(); +} + RTENTRY__ void RubberBandStretcher::setTimeRatio(double ratio) diff --git a/src/test/TestStretcher.cpp b/src/test/TestStretcher.cpp index 4d5f26e..ad3f9a8 100644 --- a/src/test/TestStretcher.cpp +++ b/src/test/TestStretcher.cpp @@ -36,6 +36,14 @@ 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_single_offline_faster) { int n = 10000; @@ -204,11 +212,82 @@ BOOST_AUTO_TEST_CASE(impulses_2_offline_faster) BOOST_TEST(peak0 == 0); BOOST_TEST(peak1 == n - 1); BOOST_TEST(peak2 == n*2 - 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_2_offline_finer) +{ + int n = 10000; + float freq = 440.f; + int rate = 44100; + RubberBandStretcher stretcher + (rate, 1, RubberBandStretcher::OptionEngineFiner, 2.0, 1.0); + + vector in(n, 0.f), out(n * 2, 0.f); + + in[0] = 1.f; + in[1] = -1.f; + + in[4999] = 1.f; + in[5000] = -1.f; + + in[9998] = 1.f; + in[9999] = -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); + + float max; + int peak0, peak1, peak2; + + for (int i = 0, max = -2.f; i < n/2; ++i) { + if (out[i] > max) { + max = out[i]; + peak0 = i; + } + } + for (int i = n/2, max = -2.f; i < (n*3)/2; ++i) { + if (out[i] > max) { + max = out[i]; + peak1 = i; + } + } + for (int i = (n*3)/2, max = -2.f; i < n*2; ++i) { + if (out[i] > max) { + max = out[i]; + peak2 = i; + } + } + + BOOST_TEST(peak0 == 0); + BOOST_TEST(peak1 == n - 1); + BOOST_TEST(peak2 == n*2 - 2); + +// std::cout << "ms\tV" << std::endl; +// for (int i = 0; i < n*2; ++i) { +// std::cout << i << "\t" << out[i] << std::endl; +// } + } #endif From 3060f37ae8a2a03716682391a74e9486770c9571 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 28 Jun 2022 14:07:05 +0100 Subject: [PATCH 154/184] Docs on R3 --- README.md | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 16671ff..9792421 100644 --- a/README.md +++ b/README.md @@ -124,8 +124,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 From fb8f021a1acd837328491c23796cf7482b6e2f04 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 29 Jun 2022 14:43:59 +0100 Subject: [PATCH 155/184] Excessive clipping can happen with perfectly normal input - make the warning less scary --- main/main.cpp | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/main/main.cpp b/main/main.cpp index d49e63c..3309490 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -865,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; @@ -983,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:: From 56c60d242005b9909b3c1512150fbfe2a9b9a73f Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 29 Jun 2022 14:44:21 +0100 Subject: [PATCH 156/184] Fix target duration in offline mode --- src/finer/R3Stretcher.cpp | 68 ++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/src/finer/R3Stretcher.cpp b/src/finer/R3Stretcher.cpp index b204990..a31e601 100644 --- a/src/finer/R3Stretcher.cpp +++ b/src/finer/R3Stretcher.cpp @@ -313,7 +313,7 @@ R3Stretcher::updateRatioFromMap() if (m_processInputDuration >= i0->first) { - m_log.log(2, "input duration surpasses pending key frame", + m_log.log(1, "input duration surpasses pending key frame", double(m_processInputDuration), double(i0->first)); auto i1 = m_keyFrameMap.upper_bound(m_processInputDuration); @@ -339,13 +339,13 @@ R3Stretcher::updateRatioFromMap() double ratio = double(toKeyFrameAtOutput) / double(toKeyFrameAtInput); - m_log.log(2, "next key frame input and output", + m_log.log(1, "next key frame input and output", double(keyFrameAtInput), double(keyFrameAtOutput)); - m_log.log(2, "current input and output", + m_log.log(1, "current input and output", double(m_processInputDuration), double(m_totalOutputDuration)); - m_log.log(2, "to next key frame input and output", + m_log.log(1, "to next key frame input and output", double(toKeyFrameAtInput), double(toKeyFrameAtOutput)); - m_log.log(2, "new ratio", ratio); + m_log.log(1, "new ratio", ratio); m_timeRatio = ratio; calculateHop(); @@ -478,6 +478,24 @@ R3Stretcher::process(const float *const *input, size_t samples, bool final) } 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 th next key frame yet. It's important that we do + // this (the first time through) before the padding + // calculation below, since it may change the effective + // starting ratio. But it has to follow the overall target + // calculation above, which uses the "global" ratio. + + if (!m_keyFrameMap.empty()) { + updateRatioFromMap(); + } if (m_mode == ProcessMode::JustCreated || m_mode == ProcessMode::Studying) { @@ -509,15 +527,6 @@ R3Stretcher::process(const float *const *input, size_t samples, bool final) m_log.log(1, "start skip is", m_startSkip); } } - - if (!m_keyFrameMap.empty()) { - if (m_mode == ProcessMode::Studying) { - m_totalTargetDuration = - size_t(round(m_studyInputDuration * getEffectiveRatio())); - m_log.log(1, "for keyframe map: study duration and target duration", m_studyInputDuration, m_totalTargetDuration); - } - updateRatioFromMap(); - } } if (final) { @@ -708,12 +717,28 @@ R3Stretcher::consume() // 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(), resampledCount); + cd->outbuf->write(cd->resampled.data(), writeCount); } else { - cd->outbuf->write(cd->mixdown.data(), validCount); + cd->outbuf->write(cd->mixdown.data(), writeCount); } if (readSpace < inhop) { @@ -727,19 +752,16 @@ R3Stretcher::consume() } } - if (m_resampler) { - m_totalOutputDuration += resampledCount; - } else { - m_totalOutputDuration += validCount; - } + m_totalOutputDuration += writeCount; if (m_startSkip > 0) { - int toSkip = std::min - (m_startSkip, m_channelData.at(0)->outbuf->getReadSpace()); + 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; From 54b10f155c35f80be03b456874c465b97278bce0 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 29 Jun 2022 16:51:18 +0100 Subject: [PATCH 157/184] Padding and key-frame fixes --- src/finer/R3Stretcher.cpp | 37 +++++++++++++------------------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/src/finer/R3Stretcher.cpp b/src/finer/R3Stretcher.cpp index a31e601..630b3ca 100644 --- a/src/finer/R3Stretcher.cpp +++ b/src/finer/R3Stretcher.cpp @@ -328,23 +328,17 @@ R3Stretcher::updateRatioFromMap() keyFrameAtOutput = m_totalTargetDuration; } -// size_t toKeyFrameAtInput = keyFrameAtInput - i0->first; -// size_t toKeyFrameAtOutput = keyFrameAtOutput - i0->second; - size_t toKeyFrameAtInput = keyFrameAtInput - m_processInputDuration; - size_t toKeyFrameAtOutput = 0; - - if (keyFrameAtOutput > m_totalOutputDuration) { - toKeyFrameAtOutput = keyFrameAtOutput - m_totalOutputDuration; - } + 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, "to next key frame input and output", - double(toKeyFrameAtInput), double(toKeyFrameAtOutput)); m_log.log(1, "new ratio", ratio); m_timeRatio = ratio; @@ -487,11 +481,9 @@ R3Stretcher::process(const float *const *input, size_t samples, bool final) } // Update this on every process round, checking whether we've - // surpassed th next key frame yet. It's important that we do - // this (the first time through) before the padding - // calculation below, since it may change the effective - // starting ratio. But it has to follow the overall target - // calculation above, which uses the "global" ratio. + // 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(); @@ -504,13 +496,11 @@ R3Stretcher::process(const float *const *input, size_t samples, bool final) createResampler(); } - // Pad to 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 half = m_guideConfiguration.longestFftSize / 2; - int pad = half + half; + // 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); @@ -518,8 +508,7 @@ R3Stretcher::process(const float *const *input, size_t samples, bool final) // NB by the time we skip this later we may have resampled // as well as stretched - - m_startSkip = half + int(round(half / m_pitchScale * m_timeRatio)); + 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; From 0fa2ed598f75425f17d22e575a696ec218079358 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 29 Jun 2022 16:51:31 +0100 Subject: [PATCH 158/184] Documentation updates --- rubberband/RubberBandStretcher.h | 88 ++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 39 deletions(-) diff --git a/rubberband/RubberBandStretcher.h b/rubberband/RubberBandStretcher.h index ebd894b..7c7f226 100644 --- a/rubberband/RubberBandStretcher.h +++ b/rubberband/RubberBandStretcher.h @@ -117,18 +117,18 @@ public: * * \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 for - * backward compatibility. 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. + * 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 added 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. + * (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 @@ -142,11 +142,12 @@ public: * 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 @@ -171,9 +172,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 @@ -189,9 +191,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 @@ -208,11 +211,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. * @@ -221,9 +226,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. @@ -239,8 +245,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. @@ -252,8 +259,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 @@ -265,9 +273,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 @@ -285,9 +294,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 From dd88c4aeab8ddb94e1a09a37203183c0bbe1f226 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 29 Jun 2022 16:51:39 +0100 Subject: [PATCH 159/184] Further tests --- src/test/TestStretcher.cpp | 278 ++++++++++++++++++++++++++++--------- 1 file changed, 210 insertions(+), 68 deletions(-) diff --git a/src/test/TestStretcher.cpp b/src/test/TestStretcher.cpp index ad3f9a8..13a0429 100644 --- a/src/test/TestStretcher.cpp +++ b/src/test/TestStretcher.cpp @@ -44,7 +44,7 @@ BOOST_AUTO_TEST_CASE(engine_version) BOOST_TEST(s3.getEngineVersion() == 3); } -BOOST_AUTO_TEST_CASE(sinusoid_unchanged_single_offline_faster) +BOOST_AUTO_TEST_CASE(sinusoid_unchanged_offline_faster) { int n = 10000; float freq = 440.f; @@ -100,7 +100,7 @@ BOOST_AUTO_TEST_CASE(sinusoid_unchanged_single_offline_faster) tt::tolerance(0.001f) << tt::per_element()); } -BOOST_AUTO_TEST_CASE(sinusoid_unchanged_single_offline_finer) +BOOST_AUTO_TEST_CASE(sinusoid_unchanged_offline_finer) { int n = 10000; float freq = 440.f; @@ -136,7 +136,7 @@ BOOST_AUTO_TEST_CASE(sinusoid_unchanged_single_offline_finer) // what these tolerances mean BOOST_TEST(out == in, - tt::tolerance(0.15f) << tt::per_element()); + 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), @@ -148,26 +148,101 @@ BOOST_AUTO_TEST_CASE(sinusoid_unchanged_single_offline_finer) // } } -#ifdef NOT_YET +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; -BOOST_AUTO_TEST_CASE(impulses_2_offline_faster) + 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; float freq = 440.f; int rate = 44100; RubberBandStretcher stretcher - (rate, 1, RubberBandStretcher::OptionEngineFaster, 2.0, 1.0); + (rate, 1, RubberBandStretcher::OptionEngineFaster); + + stretcher.setTimeRatio(2.0); vector in(n, 0.f), out(n * 2, 0.f); - in[0] = 1.f; - in[1] = -1.f; + in[100] = 1.f; + in[101] = -1.f; - in[4999] = 1.f; - in[5000] = -1.f; + in[5000] = 1.f; + in[5001] = -1.f; - in[9998] = 1.f; - in[9999] = -1.f; + in[9900] = 1.f; + in[9901] = -1.f; float *inp = in.data(), *outp = out.data(); @@ -187,31 +262,29 @@ BOOST_AUTO_TEST_CASE(impulses_2_offline_faster) BOOST_TEST(got == n * 2); BOOST_TEST(stretcher.available() == -1); - float max; int peak0, peak1, peak2; + float max; - for (int i = 0, max = -2.f; i < n/2; ++i) { - if (out[i] > max) { - max = out[i]; - peak0 = i; - } - } - for (int i = n/2, max = -2.f; i < (n*3)/2; ++i) { - if (out[i] > max) { - max = out[i]; - peak1 = i; - } - } - for (int i = (n*3)/2, max = -2.f; i < n*2; ++i) { - if (out[i] > max) { - max = out[i]; - peak2 = i; - } + max = -2.f; + for (int i = 0; i < n/2; ++i) { + if (out[i] > max) { max = out[i]; peak0 = i; } } - BOOST_TEST(peak0 == 0); - BOOST_TEST(peak1 == n - 1); - BOOST_TEST(peak2 == n*2 - 2); + 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) { @@ -220,24 +293,26 @@ BOOST_AUTO_TEST_CASE(impulses_2_offline_faster) */ } -BOOST_AUTO_TEST_CASE(impulses_2_offline_finer) +BOOST_AUTO_TEST_CASE(impulses_2x_offline_finer) { int n = 10000; float freq = 440.f; int rate = 44100; RubberBandStretcher stretcher - (rate, 1, RubberBandStretcher::OptionEngineFiner, 2.0, 1.0); + (rate, 1, RubberBandStretcher::OptionEngineFiner); + + stretcher.setTimeRatio(2.0); vector in(n, 0.f), out(n * 2, 0.f); - in[0] = 1.f; - in[1] = -1.f; + in[100] = 1.f; + in[101] = -1.f; - in[4999] = 1.f; - in[5000] = -1.f; + in[5000] = 1.f; + in[5001] = -1.f; - in[9998] = 1.f; - in[9999] = -1.f; + in[9900] = 1.f; + in[9901] = -1.f; float *inp = in.data(), *outp = out.data(); @@ -257,39 +332,106 @@ BOOST_AUTO_TEST_CASE(impulses_2_offline_finer) BOOST_TEST(got == n * 2); BOOST_TEST(stretcher.available() == -1); - float max; int peak0, peak1, peak2; - - for (int i = 0, max = -2.f; i < n/2; ++i) { - if (out[i] > max) { - max = out[i]; - peak0 = i; - } - } - for (int i = n/2, max = -2.f; i < (n*3)/2; ++i) { - if (out[i] > max) { - max = out[i]; - peak1 = i; - } - } - for (int i = (n*3)/2, max = -2.f; i < n*2; ++i) { - if (out[i] > max) { - max = out[i]; - peak2 = i; - } + float max; + + max = -2.f; + for (int i = 0; i < n/2; ++i) { + if (out[i] > max) { max = out[i]; peak0 = i; } } - BOOST_TEST(peak0 == 0); - BOOST_TEST(peak1 == n - 1); - BOOST_TEST(peak2 == n*2 - 2); + max = -2.f; + for (int i = n/2; i < (n*3)/2; ++i) { + if (out[i] > max) { max = out[i]; peak1 = i; } + } -// std::cout << "ms\tV" << std::endl; -// for (int i = 0; i < n*2; ++i) { -// std::cout << i << "\t" << out[i] << std::endl; -// } + 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; + } +*/ } -#endif +BOOST_AUTO_TEST_CASE(impulses_2x_5up_offline_finer) +{ + int n = 10000; + float freq = 440.f; + 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, peak1, peak2; + 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() From c6e4d9a3b2a2d1e84c8e4638b92bd361f476189f Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 29 Jun 2022 17:44:50 +0100 Subject: [PATCH 160/184] Documentation updates --- rubberband/RubberBandStretcher.h | 217 ++++++++++++++++++++++--------- src/RubberBandStretcher.cpp | 1 + 2 files changed, 157 insertions(+), 61 deletions(-) diff --git a/rubberband/RubberBandStretcher.h b/rubberband/RubberBandStretcher.h index 7c7f226..7fc26e2 100644 --- a/rubberband/RubberBandStretcher.h +++ b/rubberband/RubberBandStretcher.h @@ -41,49 +41,85 @@ #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 { @@ -365,6 +401,10 @@ public: // 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 { @@ -372,10 +412,34 @@ 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() { } }; @@ -413,7 +477,7 @@ public: /** * 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 cout) is not visible in the + * 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. * @@ -576,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); @@ -587,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 @@ -615,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); @@ -820,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; @@ -831,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; @@ -842,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; @@ -852,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); diff --git a/src/RubberBandStretcher.cpp b/src/RubberBandStretcher.cpp index c17e3ba..a2a6ee4 100644 --- a/src/RubberBandStretcher.cpp +++ b/src/RubberBandStretcher.cpp @@ -207,6 +207,7 @@ public: setExpectedInputDuration(size_t samples) { if (m_r2) m_r2->setExpectedInputDuration(samples); + //!!! perhaps also for R3 } void From f41c9a257e1cd985a6c4d9d027536d69cf8f41ff Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 29 Jun 2022 17:58:09 +0100 Subject: [PATCH 161/184] Update version number, other builds, and C API --- meson.build | 2 +- otherbuilds/Makefile.ios | 9 +- otherbuilds/Makefile.linux | 125 +++++++++++++------------ otherbuilds/Makefile.macos | 125 +++++++++++++------------ otherbuilds/Makefile.macos-universal | 125 +++++++++++++------------ otherbuilds/rubberband-library.vcxproj | 5 +- rubberband/RubberBandStretcher.h | 2 +- rubberband/rubberband-c.h | 10 +- single/RubberBandSingle.cpp | 5 +- src/rubberband-c.cpp | 10 ++ 10 files changed, 220 insertions(+), 198 deletions(-) diff --git a/meson.build b/meson.build index 28786fc..fb28dd7 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,7 @@ project( 'Rubber Band Library', 'c', 'cpp', - version: '3.0.0-beta2', + version: '3.0.0', license: 'GPL-2.0-or-later', default_options: [ 'cpp_std=c++11', diff --git a/otherbuilds/Makefile.ios b/otherbuilds/Makefile.ios index 938435a..d1a1977 100644 --- a/otherbuilds/Makefile.ios +++ b/otherbuilds/Makefile.ios @@ -44,18 +44,19 @@ LIBRARY_SOURCES := \ src/faster/HighFrequencyAudioCurve.cpp \ src/faster/SilentAudioCurve.cpp \ src/faster/PercussiveAudioCurve.cpp \ + src/faster/R2Stretcher.cpp \ src/faster/StretcherChannelData.cpp \ - src/faster/StretcherImpl.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/FFT.cpp \ - src/common/Allocators.cpp \ src/common/StretchCalculator.cpp \ src/common/sysutils.cpp \ src/common/Thread.cpp \ - src/finer/R3StretcherImpl.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 e721c4b..e12cbcd 100644 --- a/otherbuilds/Makefile.linux +++ b/otherbuilds/Makefile.linux @@ -33,18 +33,19 @@ LIBRARY_SOURCES := \ src/faster/HighFrequencyAudioCurve.cpp \ src/faster/SilentAudioCurve.cpp \ src/faster/PercussiveAudioCurve.cpp \ + src/faster/R2Stretcher.cpp \ src/faster/StretcherChannelData.cpp \ - src/faster/StretcherImpl.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/FFT.cpp \ - src/common/Allocators.cpp \ src/common/StretchCalculator.cpp \ src/common/sysutils.cpp \ src/common/Thread.cpp \ - src/finer/R3StretcherImpl.cpp + src/finer/R3Stretcher.cpp LIBRARY_OBJECTS := $(LIBRARY_SOURCES:.cpp=.o) LIBRARY_OBJECTS := $(LIBRARY_OBJECTS:.c=.o) @@ -69,21 +70,20 @@ depend: src/rubberband-c.o: rubberband/rubberband-c.h src/rubberband-c.o: rubberband/RubberBandStretcher.h -src/RubberBandStretcher.o: src/faster/StretcherImpl.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/sysutils.h -src/RubberBandStretcher.o: src/faster/SincWindow.h src/common/VectorOps.h -src/RubberBandStretcher.o: src/common/Allocators.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/RubberBandStretcher.o: src/finer/R3StretcherImpl.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 @@ -91,9 +91,9 @@ 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/RubberBandStretcher.o: src/common/StretchCalculator.h src/common/Log.h src/RubberBandStretcher.o: src/common/Resampler.h src/common/FixedVector.h -src/RubberBandStretcher.o: rubberband/RubberBandStretcher.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 @@ -119,15 +119,34 @@ 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/StretcherImpl.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/sysutils.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 @@ -137,33 +156,15 @@ 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/StretcherImpl.o: src/faster/StretcherImpl.h -src/faster/StretcherImpl.o: rubberband/RubberBandStretcher.h -src/faster/StretcherImpl.o: src/common/Window.h src/common/sysutils.h -src/faster/StretcherImpl.o: src/common/VectorOps.h src/common/Allocators.h -src/faster/StretcherImpl.o: src/common/FFT.h src/common/RingBuffer.h -src/faster/StretcherImpl.o: src/common/Scavenger.h src/common/Thread.h -src/faster/StretcherImpl.o: src/common/Thread.h src/common/sysutils.h -src/faster/StretcherImpl.o: src/faster/SincWindow.h src/common/VectorOps.h -src/faster/StretcherImpl.o: src/common/Allocators.h -src/faster/StretcherImpl.o: src/faster/CompoundAudioCurve.h -src/faster/StretcherImpl.o: src/faster/PercussiveAudioCurve.h -src/faster/StretcherImpl.o: src/faster/AudioCurveCalculator.h -src/faster/StretcherImpl.o: src/faster/HighFrequencyAudioCurve.h -src/faster/StretcherImpl.o: src/common/SampleFilter.h -src/faster/StretcherImpl.o: src/faster/SilentAudioCurve.h -src/faster/StretcherImpl.o: src/faster/StretcherChannelData.h -src/faster/StretcherImpl.o: src/common/StretchCalculator.h -src/faster/StretcherImpl.o: src/common/Resampler.h src/common/Profiler.h -src/faster/StretcherProcess.o: src/faster/StretcherImpl.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/sysutils.h -src/faster/StretcherProcess.o: src/faster/SincWindow.h src/common/VectorOps.h -src/faster/StretcherProcess.o: src/common/Allocators.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 @@ -171,36 +172,36 @@ 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/Resampler.h src/common/Profiler.h -src/faster/StretcherProcess.o: src/common/mathmisc.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/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/Allocators.o: src/common/Allocators.h src/common/VectorOps.h -src/common/Allocators.o: src/common/sysutils.h src/common/StretchCalculator.o: src/common/StretchCalculator.h -src/common/StretchCalculator.o: src/common/sysutils.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/R3StretcherImpl.o: src/finer/R3StretcherImpl.h -src/finer/R3StretcherImpl.o: src/finer/BinSegmenter.h -src/finer/R3StretcherImpl.o: src/finer/BinClassifier.h -src/finer/R3StretcherImpl.o: src/common/Allocators.h -src/finer/R3StretcherImpl.o: src/common/MovingMedian.h -src/finer/R3StretcherImpl.o: src/common/SampleFilter.h -src/finer/R3StretcherImpl.o: src/common/FixedVector.h src/common/Allocators.h -src/finer/R3StretcherImpl.o: src/common/VectorOps.h src/common/sysutils.h -src/finer/R3StretcherImpl.o: src/common/SingleThreadRingBuffer.h -src/finer/R3StretcherImpl.o: src/common/RingBuffer.h -src/finer/R3StretcherImpl.o: src/common/HistogramFilter.h -src/finer/R3StretcherImpl.o: src/common/mathmisc.h src/finer/Guide.h -src/finer/R3StretcherImpl.o: src/finer/Peak.h src/finer/PhaseAdvance.h -src/finer/R3StretcherImpl.o: src/common/StretchCalculator.h -src/finer/R3StretcherImpl.o: src/common/Resampler.h src/common/FFT.h -src/finer/R3StretcherImpl.o: src/common/FixedVector.h src/common/Window.h -src/finer/R3StretcherImpl.o: rubberband/RubberBandStretcher.h -src/finer/R3StretcherImpl.o: src/common/VectorOpsComplex.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 db68eae..5819dbd 100644 --- a/otherbuilds/Makefile.macos +++ b/otherbuilds/Makefile.macos @@ -33,18 +33,19 @@ LIBRARY_SOURCES := \ src/faster/HighFrequencyAudioCurve.cpp \ src/faster/SilentAudioCurve.cpp \ src/faster/PercussiveAudioCurve.cpp \ + src/faster/R2Stretcher.cpp \ src/faster/StretcherChannelData.cpp \ - src/faster/StretcherImpl.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/FFT.cpp \ - src/common/Allocators.cpp \ src/common/StretchCalculator.cpp \ src/common/sysutils.cpp \ src/common/Thread.cpp \ - src/finer/R3StretcherImpl.cpp + src/finer/R3Stretcher.cpp LIBRARY_OBJECTS := $(LIBRARY_SOURCES:.cpp=.o) LIBRARY_OBJECTS := $(LIBRARY_OBJECTS:.c=.o) @@ -70,21 +71,20 @@ depend: src/rubberband-c.o: rubberband/rubberband-c.h src/rubberband-c.o: rubberband/RubberBandStretcher.h -src/RubberBandStretcher.o: src/faster/StretcherImpl.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/sysutils.h -src/RubberBandStretcher.o: src/faster/SincWindow.h src/common/VectorOps.h -src/RubberBandStretcher.o: src/common/Allocators.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/RubberBandStretcher.o: src/finer/R3StretcherImpl.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 @@ -92,9 +92,9 @@ 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/RubberBandStretcher.o: src/common/StretchCalculator.h src/common/Log.h src/RubberBandStretcher.o: src/common/Resampler.h src/common/FixedVector.h -src/RubberBandStretcher.o: rubberband/RubberBandStretcher.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 @@ -120,15 +120,34 @@ 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/StretcherImpl.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/sysutils.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 @@ -138,33 +157,15 @@ 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/StretcherImpl.o: src/faster/StretcherImpl.h -src/faster/StretcherImpl.o: rubberband/RubberBandStretcher.h -src/faster/StretcherImpl.o: src/common/Window.h src/common/sysutils.h -src/faster/StretcherImpl.o: src/common/VectorOps.h src/common/Allocators.h -src/faster/StretcherImpl.o: src/common/FFT.h src/common/RingBuffer.h -src/faster/StretcherImpl.o: src/common/Scavenger.h src/common/Thread.h -src/faster/StretcherImpl.o: src/common/Thread.h src/common/sysutils.h -src/faster/StretcherImpl.o: src/faster/SincWindow.h src/common/VectorOps.h -src/faster/StretcherImpl.o: src/common/Allocators.h -src/faster/StretcherImpl.o: src/faster/CompoundAudioCurve.h -src/faster/StretcherImpl.o: src/faster/PercussiveAudioCurve.h -src/faster/StretcherImpl.o: src/faster/AudioCurveCalculator.h -src/faster/StretcherImpl.o: src/faster/HighFrequencyAudioCurve.h -src/faster/StretcherImpl.o: src/common/SampleFilter.h -src/faster/StretcherImpl.o: src/faster/SilentAudioCurve.h -src/faster/StretcherImpl.o: src/faster/StretcherChannelData.h -src/faster/StretcherImpl.o: src/common/StretchCalculator.h -src/faster/StretcherImpl.o: src/common/Resampler.h src/common/Profiler.h -src/faster/StretcherProcess.o: src/faster/StretcherImpl.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/sysutils.h -src/faster/StretcherProcess.o: src/faster/SincWindow.h src/common/VectorOps.h -src/faster/StretcherProcess.o: src/common/Allocators.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 @@ -172,36 +173,36 @@ 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/Resampler.h src/common/Profiler.h -src/faster/StretcherProcess.o: src/common/mathmisc.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/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/Allocators.o: src/common/Allocators.h src/common/VectorOps.h -src/common/Allocators.o: src/common/sysutils.h src/common/StretchCalculator.o: src/common/StretchCalculator.h -src/common/StretchCalculator.o: src/common/sysutils.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/R3StretcherImpl.o: src/finer/R3StretcherImpl.h -src/finer/R3StretcherImpl.o: src/finer/BinSegmenter.h -src/finer/R3StretcherImpl.o: src/finer/BinClassifier.h -src/finer/R3StretcherImpl.o: src/common/Allocators.h -src/finer/R3StretcherImpl.o: src/common/MovingMedian.h -src/finer/R3StretcherImpl.o: src/common/SampleFilter.h -src/finer/R3StretcherImpl.o: src/common/FixedVector.h src/common/Allocators.h -src/finer/R3StretcherImpl.o: src/common/VectorOps.h src/common/sysutils.h -src/finer/R3StretcherImpl.o: src/common/SingleThreadRingBuffer.h -src/finer/R3StretcherImpl.o: src/common/RingBuffer.h -src/finer/R3StretcherImpl.o: src/common/HistogramFilter.h -src/finer/R3StretcherImpl.o: src/common/mathmisc.h src/finer/Guide.h -src/finer/R3StretcherImpl.o: src/finer/Peak.h src/finer/PhaseAdvance.h -src/finer/R3StretcherImpl.o: src/common/StretchCalculator.h -src/finer/R3StretcherImpl.o: src/common/Resampler.h src/common/FFT.h -src/finer/R3StretcherImpl.o: src/common/FixedVector.h src/common/Window.h -src/finer/R3StretcherImpl.o: rubberband/RubberBandStretcher.h -src/finer/R3StretcherImpl.o: src/common/VectorOpsComplex.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 69e6180..5513ff7 100644 --- a/otherbuilds/Makefile.macos-universal +++ b/otherbuilds/Makefile.macos-universal @@ -33,18 +33,19 @@ LIBRARY_SOURCES := \ src/faster/HighFrequencyAudioCurve.cpp \ src/faster/SilentAudioCurve.cpp \ src/faster/PercussiveAudioCurve.cpp \ + src/faster/R2Stretcher.cpp \ src/faster/StretcherChannelData.cpp \ - src/faster/StretcherImpl.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/FFT.cpp \ - src/common/Allocators.cpp \ src/common/StretchCalculator.cpp \ src/common/sysutils.cpp \ src/common/Thread.cpp \ - src/finer/R3StretcherImpl.cpp + src/finer/R3Stretcher.cpp LIBRARY_OBJECTS := $(LIBRARY_SOURCES:.cpp=.o) LIBRARY_OBJECTS := $(LIBRARY_OBJECTS:.c=.o) @@ -70,21 +71,20 @@ depend: src/rubberband-c.o: rubberband/rubberband-c.h src/rubberband-c.o: rubberband/RubberBandStretcher.h -src/RubberBandStretcher.o: src/faster/StretcherImpl.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/sysutils.h -src/RubberBandStretcher.o: src/faster/SincWindow.h src/common/VectorOps.h -src/RubberBandStretcher.o: src/common/Allocators.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/RubberBandStretcher.o: src/finer/R3StretcherImpl.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 @@ -92,9 +92,9 @@ 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/RubberBandStretcher.o: src/common/StretchCalculator.h src/common/Log.h src/RubberBandStretcher.o: src/common/Resampler.h src/common/FixedVector.h -src/RubberBandStretcher.o: rubberband/RubberBandStretcher.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 @@ -120,15 +120,34 @@ 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/StretcherImpl.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/sysutils.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 @@ -138,33 +157,15 @@ 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/StretcherImpl.o: src/faster/StretcherImpl.h -src/faster/StretcherImpl.o: rubberband/RubberBandStretcher.h -src/faster/StretcherImpl.o: src/common/Window.h src/common/sysutils.h -src/faster/StretcherImpl.o: src/common/VectorOps.h src/common/Allocators.h -src/faster/StretcherImpl.o: src/common/FFT.h src/common/RingBuffer.h -src/faster/StretcherImpl.o: src/common/Scavenger.h src/common/Thread.h -src/faster/StretcherImpl.o: src/common/Thread.h src/common/sysutils.h -src/faster/StretcherImpl.o: src/faster/SincWindow.h src/common/VectorOps.h -src/faster/StretcherImpl.o: src/common/Allocators.h -src/faster/StretcherImpl.o: src/faster/CompoundAudioCurve.h -src/faster/StretcherImpl.o: src/faster/PercussiveAudioCurve.h -src/faster/StretcherImpl.o: src/faster/AudioCurveCalculator.h -src/faster/StretcherImpl.o: src/faster/HighFrequencyAudioCurve.h -src/faster/StretcherImpl.o: src/common/SampleFilter.h -src/faster/StretcherImpl.o: src/faster/SilentAudioCurve.h -src/faster/StretcherImpl.o: src/faster/StretcherChannelData.h -src/faster/StretcherImpl.o: src/common/StretchCalculator.h -src/faster/StretcherImpl.o: src/common/Resampler.h src/common/Profiler.h -src/faster/StretcherProcess.o: src/faster/StretcherImpl.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/sysutils.h -src/faster/StretcherProcess.o: src/faster/SincWindow.h src/common/VectorOps.h -src/faster/StretcherProcess.o: src/common/Allocators.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 @@ -172,36 +173,36 @@ 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/Resampler.h src/common/Profiler.h -src/faster/StretcherProcess.o: src/common/mathmisc.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/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/Allocators.o: src/common/Allocators.h src/common/VectorOps.h -src/common/Allocators.o: src/common/sysutils.h src/common/StretchCalculator.o: src/common/StretchCalculator.h -src/common/StretchCalculator.o: src/common/sysutils.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/R3StretcherImpl.o: src/finer/R3StretcherImpl.h -src/finer/R3StretcherImpl.o: src/finer/BinSegmenter.h -src/finer/R3StretcherImpl.o: src/finer/BinClassifier.h -src/finer/R3StretcherImpl.o: src/common/Allocators.h -src/finer/R3StretcherImpl.o: src/common/MovingMedian.h -src/finer/R3StretcherImpl.o: src/common/SampleFilter.h -src/finer/R3StretcherImpl.o: src/common/FixedVector.h src/common/Allocators.h -src/finer/R3StretcherImpl.o: src/common/VectorOps.h src/common/sysutils.h -src/finer/R3StretcherImpl.o: src/common/SingleThreadRingBuffer.h -src/finer/R3StretcherImpl.o: src/common/RingBuffer.h -src/finer/R3StretcherImpl.o: src/common/HistogramFilter.h -src/finer/R3StretcherImpl.o: src/common/mathmisc.h src/finer/Guide.h -src/finer/R3StretcherImpl.o: src/finer/Peak.h src/finer/PhaseAdvance.h -src/finer/R3StretcherImpl.o: src/common/StretchCalculator.h -src/finer/R3StretcherImpl.o: src/common/Resampler.h src/common/FFT.h -src/finer/R3StretcherImpl.o: src/common/FixedVector.h src/common/Window.h -src/finer/R3StretcherImpl.o: rubberband/RubberBandStretcher.h -src/finer/R3StretcherImpl.o: src/common/VectorOpsComplex.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/rubberband-library.vcxproj b/otherbuilds/rubberband-library.vcxproj index 51a2c33..3f84bb1 100644 --- a/otherbuilds/rubberband-library.vcxproj +++ b/otherbuilds/rubberband-library.vcxproj @@ -147,17 +147,18 @@ - + + - + diff --git a/rubberband/RubberBandStretcher.h b/rubberband/RubberBandStretcher.h index 7fc26e2..b5dc29a 100644 --- a/rubberband/RubberBandStretcher.h +++ b/rubberband/RubberBandStretcher.h @@ -24,7 +24,7 @@ #ifndef RUBBERBAND_STRETCHER_H #define RUBBERBAND_STRETCHER_H -#define RUBBERBAND_VERSION "3.0.0-beta2" +#define RUBBERBAND_VERSION "3.0.0" #define RUBBERBAND_API_MAJOR_VERSION 2 #define RUBBERBAND_API_MINOR_VERSION 7 diff --git a/rubberband/rubberband-c.h b/rubberband/rubberband-c.h index 231f99a..c1e8a4c 100644 --- a/rubberband/rubberband-c.h +++ b/rubberband/rubberband-c.h @@ -28,7 +28,7 @@ extern "C" { #endif -#define RUBBERBAND_VERSION "3.0.0-beta2" +#define RUBBERBAND_VERSION "3.0.0" #define RUBBERBAND_API_MAJOR_VERSION 2 #define RUBBERBAND_API_MINOR_VERSION 7 @@ -90,7 +90,10 @@ enum RubberBandOption { RubberBandOptionPitchHighConsistency = 0x04000000, RubberBandOptionChannelsApart = 0x00000000, - RubberBandOptionChannelsTogether = 0x10000000 + RubberBandOptionChannelsTogether = 0x10000000, + + RubberBandOptionEngineFaster = 0x00000000, + RubberBandOptionEngineFiner = 0x20000000 }; typedef int RubberBandOptions; @@ -114,6 +117,9 @@ 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 bea6a76..91d78d0 100644 --- a/single/RubberBandSingle.cpp +++ b/single/RubberBandSingle.cpp @@ -61,6 +61,7 @@ #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" @@ -70,9 +71,9 @@ #include "../src/common/sysutils.cpp" #include "../src/common/Thread.cpp" #include "../src/faster/StretcherChannelData.cpp" -#include "../src/faster/R2StretcherImpl.cpp" +#include "../src/faster/R2Stretcher.cpp" #include "../src/faster/StretcherProcess.cpp" -#include "../src/finer/R3StretcherImpl.cpp" +#include "../src/finer/R3Stretcher.cpp" #include "../src/RubberBandStretcher.cpp" #include "../src/rubberband-c.cpp" diff --git a/src/rubberband-c.cpp b/src/rubberband-c.cpp index d7e71d0..cfa4b38 100644 --- a/src/rubberband-c.cpp +++ b/src/rubberband-c.cpp @@ -73,6 +73,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(); From ddbb814c6811def14943276e9f2a770fdf29f254 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 29 Jun 2022 18:04:10 +0100 Subject: [PATCH 162/184] Update dotnet build file --- dotnet/rubberband-library.vcxproj | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dotnet/rubberband-library.vcxproj b/dotnet/rubberband-library.vcxproj index 51a2c33..3f84bb1 100644 --- a/dotnet/rubberband-library.vcxproj +++ b/dotnet/rubberband-library.vcxproj @@ -147,17 +147,18 @@ - + + - + From 51631289db685944e3f584d52b1373505d075ba5 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 29 Jun 2022 18:33:57 +0100 Subject: [PATCH 163/184] Need C++11 standard --- otherbuilds/check.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/otherbuilds/check.sh b/otherbuilds/check.sh index ed495a0..47758a2 100755 --- a/otherbuilds/check.sh +++ b/otherbuilds/check.sh @@ -9,13 +9,13 @@ if [ ! -d /Applications ]; then make -f otherbuilds/Makefile.linux echo " *** Linking against static library" - g++ main/main.cpp lib/librubberband.a -I. -Isrc -o test -lsndfile -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++ -O3 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 -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++ -O3 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 From 08861c6e76bc6c192d0e5095cfa4d2ac9459c519 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 30 Jun 2022 08:26:47 +0100 Subject: [PATCH 164/184] Add getEngineVersion to C API --- rubberband/rubberband-c.h | 2 ++ src/rubberband-c.cpp | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/rubberband/rubberband-c.h b/rubberband/rubberband-c.h index c1e8a4c..8aea543 100644 --- a/rubberband/rubberband-c.h +++ b/rubberband/rubberband-c.h @@ -111,6 +111,8 @@ 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); diff --git a/src/rubberband-c.cpp b/src/rubberband-c.cpp index cfa4b38..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); From 65819f8876bc8bbe55c572b4d860bdba22db0fa2 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 30 Jun 2022 08:30:32 +0100 Subject: [PATCH 165/184] Move stub unistd file to new location of code that may include it --- src/{ => ext}/getopt/unistd.h | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{ => ext}/getopt/unistd.h (100%) 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 From d2126c8a2ee5e5cf4652c6b9d0234368180f348d Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 30 Jun 2022 09:32:39 +0100 Subject: [PATCH 166/184] Fix array overrun --- src/finer/Guide.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/finer/Guide.h b/src/finer/Guide.h index a7e5538..8162b01 100644 --- a/src/finer/Guide.h +++ b/src/finer/Guide.h @@ -458,12 +458,17 @@ protected: } 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 (magnitudes[b+1] < magnitudes[b]) { + if (b < n && magnitudes[b+1] < magnitudes[b]) { ++b; - } else if (magnitudes[b-1] < magnitudes[b]) { + } else if (b > 0 && magnitudes[b-1] < magnitudes[b]) { --b; } else { break; From 829fdfd2f93d243bb7faddff3de4f64f0e30dbb9 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 30 Jun 2022 11:49:18 +0100 Subject: [PATCH 167/184] Fix compiler warnings in tests --- src/test/TestStretcher.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/test/TestStretcher.cpp b/src/test/TestStretcher.cpp index 13a0429..49b0ce1 100644 --- a/src/test/TestStretcher.cpp +++ b/src/test/TestStretcher.cpp @@ -226,7 +226,6 @@ BOOST_AUTO_TEST_CASE(sinusoid_2x_offline_finer) BOOST_AUTO_TEST_CASE(impulses_2x_offline_faster) { int n = 10000; - float freq = 440.f; int rate = 44100; RubberBandStretcher stretcher (rate, 1, RubberBandStretcher::OptionEngineFaster); @@ -262,7 +261,7 @@ BOOST_AUTO_TEST_CASE(impulses_2x_offline_faster) BOOST_TEST(got == n * 2); BOOST_TEST(stretcher.available() == -1); - int peak0, peak1, peak2; + int peak0 = -1, peak1 = -1, peak2 = -1; float max; max = -2.f; @@ -296,7 +295,6 @@ BOOST_AUTO_TEST_CASE(impulses_2x_offline_faster) BOOST_AUTO_TEST_CASE(impulses_2x_offline_finer) { int n = 10000; - float freq = 440.f; int rate = 44100; RubberBandStretcher stretcher (rate, 1, RubberBandStretcher::OptionEngineFiner); @@ -332,7 +330,7 @@ BOOST_AUTO_TEST_CASE(impulses_2x_offline_finer) BOOST_TEST(got == n * 2); BOOST_TEST(stretcher.available() == -1); - int peak0, peak1, peak2; + int peak0 = -1, peak1 = -1, peak2 = -1; float max; max = -2.f; @@ -366,7 +364,6 @@ BOOST_AUTO_TEST_CASE(impulses_2x_offline_finer) BOOST_AUTO_TEST_CASE(impulses_2x_5up_offline_finer) { int n = 10000; - float freq = 440.f; int rate = 44100; RubberBandStretcher stretcher (rate, 1, RubberBandStretcher::OptionEngineFiner); @@ -403,7 +400,7 @@ BOOST_AUTO_TEST_CASE(impulses_2x_5up_offline_finer) BOOST_TEST(got == n * 2); BOOST_TEST(stretcher.available() == -1); - int peak0, peak1, peak2; + int peak0 = -1, peak1 = -1, peak2 = -1; float max; max = -2.f; From 8e4fb68283c4fe02a7f43c67e34f3fa440702c1a Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 30 Jun 2022 12:03:07 +0100 Subject: [PATCH 168/184] Break out compiling instructions into COMPILING file separately --- COMPILING.md | 524 ++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 526 +-------------------------------------------------- 2 files changed, 530 insertions(+), 520 deletions(-) create mode 100644 COMPILING.md 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/README.md b/README.md index 9792421..71e934d 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ 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 for more details. * FFTW3 - GPL; proprietary licence needed for redistribution * Intel IPP - Proprietary; licence needed for redistribution @@ -68,15 +68,9 @@ our knowledge. See also the end of this README for detailed terms. 1. Code components 2. Using the Rubber Band command-line tool 3. Using 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 + +Refer to the file COMPILING.md for details about how to compile the +library. ## 1. Code components @@ -87,7 +81,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 @@ -175,512 +170,3 @@ 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. -``` From 2e62b2e2372cb76020e58e965e4c27d9aa125c7f Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 30 Jun 2022 12:04:45 +0100 Subject: [PATCH 169/184] Make the compiling reference a bit more obvious --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 71e934d..e7a286c 100644 --- a/README.md +++ b/README.md @@ -63,15 +63,18 @@ our knowledge. See also the file COMPILING.md for more details. * Pommier math functions - BSD-like +## Compiling Rubber Band Library + +Please refer to the file COMPILING.md for details about 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 -Refer to the file COMPILING.md for details about how to compile the -library. - ## 1. Code components From 32f9ddf26272f957d19ffc008984d1eb92eb4101 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 30 Jun 2022 12:07:39 +0100 Subject: [PATCH 170/184] Do relative links like this work? --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e7a286c..daa3f9e 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 file COMPILING.md for more details. +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 @@ -65,8 +66,8 @@ our knowledge. See also the file COMPILING.md for more details. ## Compiling Rubber Band Library -Please refer to the file COMPILING.md for details about how to -configure and build the library. +Please refer to the file [COMPILING.md](COMPILING.md) for details of +how to configure and build the library. ## Contents of this README From 1ed625b5d3b7cd13a8bd40c60a2511f1ff05b262 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 30 Jun 2022 12:14:01 +0100 Subject: [PATCH 171/184] Further links --- README.md | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index daa3f9e..fd57943 100644 --- a/README.md +++ b/README.md @@ -166,11 +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. +**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/) + From 75c1bdc01562b4bbb7f22227843c78eb2b93bb73 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 30 Jun 2022 14:00:14 +0100 Subject: [PATCH 172/184] Fix speex build --- .build.yml | 13 +++++++++++++ meson.build | 2 +- src/common/Resampler.cpp | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.build.yml b/.build.yml index 8425bcf..fadd5e2 100644 --- a/.build.yml +++ b/.build.yml @@ -15,10 +15,23 @@ tasks: - setup: | cd rubberband meson build + meson build_speex -Dresampler=speex + meson build_libsamplerate -Dresampler=libsamplerate + meson build_fftw -Dfft=fftw - 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 ./otherbuilds/check.sh triggers: - action: email diff --git a/meson.build b/meson.build index fb28dd7..812a97e 100644 --- a/meson.build +++ b/meson.build @@ -241,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' diff --git a/src/common/Resampler.cpp b/src/common/Resampler.cpp index 83ba0df..32444a7 100644 --- a/src/common/Resampler.cpp +++ b/src/common/Resampler.cpp @@ -54,7 +54,7 @@ #endif #ifdef USE_SPEEX -#include "../speex/speex_resampler.h" +#include "../ext/speex/speex_resampler.h" #endif #ifdef USE_BQRESAMPLER From 4c07eda216933da7a756e20205a2a8d065015332 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 30 Jun 2022 14:37:15 +0100 Subject: [PATCH 173/184] Add libboost for tests --- .build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.build.yml b/.build.yml index fadd5e2..0dededc 100644 --- a/.build.yml +++ b/.build.yml @@ -7,6 +7,7 @@ packages: - ladspa-sdk - lv2-dev - vamp-plugin-sdk + - libboost - meson - ninja-build sources: From fc5aaad929a3f055ab63ebec5744759833d5fa45 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 30 Jun 2022 14:48:09 +0100 Subject: [PATCH 174/184] Correct (I hope) package name --- .build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.build.yml b/.build.yml index 0dededc..3cb171d 100644 --- a/.build.yml +++ b/.build.yml @@ -7,7 +7,7 @@ packages: - ladspa-sdk - lv2-dev - vamp-plugin-sdk - - libboost + - libboost-test-dev - meson - ninja-build sources: From 68a9e4f8d588edab4f7503bc45d4275691970efb Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 30 Jun 2022 15:05:19 +0100 Subject: [PATCH 175/184] Require specific Boost test version, and don't check for it manually as we can't do the version test. (We use quite a recent version of the test macros) --- meson.build | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/meson.build b/meson.build index 812a97e..f7146e6 100644 --- a/meson.build +++ b/meson.build @@ -115,7 +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_unit_test_framework', 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) @@ -328,13 +328,6 @@ if not sndfile_dep.found() endif have_sndfile = sndfile_dep.found() -if not boost_unit_test_dep.found() - boost_unit_test_dep = cpp.find_library('boost_unit_test_framework', - dirs: get_option('extra_lib_dirs'), - has_headers: ['boost/test/unit_test.hpp'], - header_args: extra_include_args, - required: false) -endif have_boost_unit_test = boost_unit_test_dep.found() From b6be35158b8245fa165f237e04d04628d404a347 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 30 Jun 2022 15:07:09 +0100 Subject: [PATCH 176/184] Experiment with the single-file build in CI --- .appveyor.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 8523478..e11acd4 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -19,5 +19,8 @@ build_script: - 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 + - .\test_single.exe -V From e9403fb52d429c725d0b21df5a4b5676f8557944 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 30 Jun 2022 15:21:39 +0100 Subject: [PATCH 177/184] Quoting --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index e11acd4..b352851 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -22,5 +22,5 @@ build_script: # 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 + - 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 - .\test_single.exe -V From 1afb4acdd520f4584aa0e87e30853b0b42d2d6f7 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 30 Jun 2022 15:21:46 +0100 Subject: [PATCH 178/184] Update CHANGELOG for 3.0.0 --- CHANGELOG | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) 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 From 8d4d8a39bc7ec4ef010ae63a93ba99bcd9875395 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 30 Jun 2022 15:27:25 +0100 Subject: [PATCH 179/184] Actually picking up the runtime libraries may be a bigger task --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index b352851..1c826c1 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -23,4 +23,4 @@ build_script: - 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 - - .\test_single.exe -V + From 335aef35c91e59f632673618c0ba015887f9be08 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 1 Jul 2022 12:01:06 +0100 Subject: [PATCH 180/184] The Boost meson module sets this on the command-line, so avoid compiler warning when redefining --- src/test/TestAllocators.cpp | 2 ++ src/test/TestBinClassifier.cpp | 2 ++ src/test/TestFFT.cpp | 2 ++ src/test/TestResampler.cpp | 2 ++ src/test/TestSignalBits.cpp | 2 ++ src/test/TestStretchCalculator.cpp | 2 ++ src/test/TestStretcher.cpp | 2 ++ src/test/TestVectorOps.cpp | 2 ++ src/test/TestVectorOpsComplex.cpp | 2 ++ src/test/test.cpp | 2 ++ 10 files changed, 20 insertions(+) diff --git a/src/test/TestAllocators.cpp b/src/test/TestAllocators.cpp index 32f547e..71f0548 100644 --- a/src/test/TestAllocators.cpp +++ b/src/test/TestAllocators.cpp @@ -21,7 +21,9 @@ 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" diff --git a/src/test/TestBinClassifier.cpp b/src/test/TestBinClassifier.cpp index f1786ab..303ffe7 100644 --- a/src/test/TestBinClassifier.cpp +++ b/src/test/TestBinClassifier.cpp @@ -21,7 +21,9 @@ 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 diff --git a/src/test/TestFFT.cpp b/src/test/TestFFT.cpp index 78b5df2..d915778 100644 --- a/src/test/TestFFT.cpp +++ b/src/test/TestFFT.cpp @@ -21,7 +21,9 @@ 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" diff --git a/src/test/TestResampler.cpp b/src/test/TestResampler.cpp index 774c2b5..b7b5bb3 100644 --- a/src/test/TestResampler.cpp +++ b/src/test/TestResampler.cpp @@ -21,7 +21,9 @@ 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" diff --git a/src/test/TestSignalBits.cpp b/src/test/TestSignalBits.cpp index bd56248..70c073f 100644 --- a/src/test/TestSignalBits.cpp +++ b/src/test/TestSignalBits.cpp @@ -21,7 +21,9 @@ 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" diff --git a/src/test/TestStretchCalculator.cpp b/src/test/TestStretchCalculator.cpp index ffe7d44..ae48056 100644 --- a/src/test/TestStretchCalculator.cpp +++ b/src/test/TestStretchCalculator.cpp @@ -21,7 +21,9 @@ 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" diff --git a/src/test/TestStretcher.cpp b/src/test/TestStretcher.cpp index 49b0ce1..37fb569 100644 --- a/src/test/TestStretcher.cpp +++ b/src/test/TestStretcher.cpp @@ -21,7 +21,9 @@ 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" diff --git a/src/test/TestVectorOps.cpp b/src/test/TestVectorOps.cpp index 615a89f..8f2803f 100644 --- a/src/test/TestVectorOps.cpp +++ b/src/test/TestVectorOps.cpp @@ -21,7 +21,9 @@ 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" diff --git a/src/test/TestVectorOpsComplex.cpp b/src/test/TestVectorOpsComplex.cpp index dd73cd8..0eebf5d 100644 --- a/src/test/TestVectorOpsComplex.cpp +++ b/src/test/TestVectorOpsComplex.cpp @@ -21,7 +21,9 @@ 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" diff --git a/src/test/test.cpp b/src/test/test.cpp index e5fda23..b290077 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -22,5 +22,7 @@ */ #define BOOST_TEST_MODULE RubberBand +#ifndef BOOST_TEST_DYN_LINK #define BOOST_TEST_DYN_LINK +#endif #include From ee3e7e45ff6ffda38ece6c83f5650ebacf1e217e Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 1 Jul 2022 13:14:21 +0100 Subject: [PATCH 181/184] Fix & test kissfft build --- .build.yml | 4 ++++ meson.build | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.build.yml b/.build.yml index 3cb171d..52785d3 100644 --- a/.build.yml +++ b/.build.yml @@ -19,6 +19,7 @@ tasks: 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 @@ -33,6 +34,9 @@ tasks: 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/meson.build b/meson.build index f7146e6..964b696 100644 --- a/meson.build +++ b/meson.build @@ -172,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() From cab969f14c7da7143178b859cf02cb4488c752a7 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 1 Jul 2022 13:52:34 +0100 Subject: [PATCH 182/184] Added tag v3.0.0-beta3 for changeset 58b588a580a1 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index cd4ed79..292ea37 100644 --- a/.hgtags +++ b/.hgtags @@ -20,3 +20,4 @@ fa6a54be7e6bf0c5adffd19ccec622481a8140a5 v1.8.2 acc04c20175ebc3f8138c4e14c9108cb33024fb1 v3.0.0-beta2 acc04c20175ebc3f8138c4e14c9108cb33024fb1 v3.0.0-beta2 ed9acf241b1076e84ba0b41dc9d8edd904f69f25 v3.0.0-beta2 +58b588a580a126adb16de96f8eade96111758085 v3.0.0-beta3 From 7bb916e71e7dd1b34b04721e41b6cfb1b3f81d15 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 1 Jul 2022 13:53:51 +0100 Subject: [PATCH 183/184] Beta3 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 964b696..38e7b1d 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,7 @@ project( 'Rubber Band Library', 'c', 'cpp', - version: '3.0.0', + version: '3.0.0-beta3', license: 'GPL-2.0-or-later', default_options: [ 'cpp_std=c++11', From 67cb55c37517c2b2a2c41ec013bcee046df9c38a Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 1 Jul 2022 13:53:56 +0100 Subject: [PATCH 184/184] Added tag v3.0.0-beta3 for changeset 78a701fb6daa --- .hgtags | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.hgtags b/.hgtags index 292ea37..8fe0780 100644 --- a/.hgtags +++ b/.hgtags @@ -21,3 +21,5 @@ 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