From 024b159c59127a567ce41b8754d846e079248215 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 8 Jan 2021 17:08:26 +0000 Subject: [PATCH 01/32] Branch for freqmap specifically From a75e6fd678642e5da72ae2ab15f8b3a09ef432c4 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 8 Mar 2021 13:14:49 +0000 Subject: [PATCH 02/32] Re-apply freqmap, since the merge from default lost it as the commit that removed it was after we branched! --- main/main.cpp | 520 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 345 insertions(+), 175 deletions(-) diff --git a/main/main.cpp b/main/main.cpp index f22383e..4adfc2d 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -105,7 +105,10 @@ int main(int argc, char **argv) bool haveRatio = false; - std::string mapfile; + std::string timeMapFile; + std::string freqMapFile; + std::string pitchMapFile; + bool freqOrPitchMapSpecified = false; enum { NoTransients, @@ -119,6 +122,8 @@ int main(int argc, char **argv) SoftDetector } detector = CompoundDetector; + bool ignoreClipping = false; + while (1) { int optionIndex = 0; @@ -151,6 +156,9 @@ int main(int argc, char **argv) { "threads", 0, 0, '@' }, { "quiet", 0, 0, 'q' }, { "timemap", 1, 0, 'M' }, + { "freqmap", 1, 0, 'Q' }, + { "pitchmap", 1, 0, 'C' }, + { "ignore-clipping", 0, 0, 'i' }, { 0, 0, 0, 0 } }; @@ -171,7 +179,7 @@ int main(int argc, char **argv) case 'R': realtime = true; break; case 'L': precise = false; break; case 'P': precise = true; break; - case 'F': formant = true; break; + case 'F': formant = true; break; case '0': threading = 1; break; case '@': threading = 2; break; case '1': transients = NoTransients; crispchanged = true; break; @@ -186,7 +194,10 @@ int main(int argc, char **argv) case '%': hqpitch = true; break; case 'c': crispness = atoi(optarg); break; case 'q': quiet = true; break; - case 'M': mapfile = optarg; break; + case 'M': timeMapFile = optarg; break; + case 'Q': freqMapFile = optarg; freqOrPitchMapSpecified = true; break; + case 'C': pitchMapFile = optarg; freqOrPitchMapSpecified = true; break; + case 'i': ignoreClipping = true; break; default: help = true; break; } } @@ -196,6 +207,15 @@ int main(int argc, char **argv) return 0; } + if (freqOrPitchMapSpecified) { + if (freqMapFile != "" && pitchMapFile != "") { + cerr << "ERROR: Please specify either pitch map or frequency map, not both" << endl; + return 1; + } + haveRatio = true; + realtime = true; + } + if (help || !haveRatio || optind + 2 != argc) { cerr << endl; cerr << "Rubber Band" << endl; @@ -214,23 +234,45 @@ int main(int argc, char **argv) cerr << " -p, --pitch Raise pitch by X semitones, or" << endl; cerr << " -f, --frequency Change frequency by multiple X" << endl; cerr << endl; - cerr << " -M, --timemap Use file F as the source for key frame map" << endl; + cerr << "The following options provide ways of making the time and frequency ratios" << endl; + cerr << "change during the audio." << endl; cerr << endl; - cerr << "A map file consists of a series of lines each having two numbers separated" << endl; - cerr << "by a single space. These are source and target sample frame numbers for fixed" << endl; - cerr << "time points within the audio data, defining a varying stretch factor through" << endl; - cerr << "the audio. You must specify an overall stretch factor using e.g. -t as well." << endl; + cerr << " -M, --timemap Use file F as the source for time map" << endl; cerr << endl; - cerr << "The following options provide a simple way to adjust the sound. See below" << endl; + cerr << " A time map (or key-frame map) file contains a series of lines, each with two" << endl; + cerr << " sample frame numbers separated by a single space. These are source and" << endl; + cerr << " target frames for fixed time points within the audio data, defining a varying" << endl; + cerr << " stretch factor through the audio. When supplying a time map you must specify" << endl; + cerr << " an overall stretch factor using -t, -T, or -D as well, to determine the" << endl; + cerr << " total output duration." << endl; + cerr << endl; + cerr << " --pitchmap Use file F as the source for pitch map" << endl; + cerr << endl; + cerr << " A pitch map file contains a series of lines, each with two values: the input" << endl; + cerr << " sample frame number and a pitch offset in semitones, separated by a single" << endl; + cerr << " space. These specify a varying pitch factor through the audio. The offsets" << endl; + cerr << " are all relative to an initial offset specified by the pitch or frequency" << endl; + cerr << " option, or relative to no shift if neither was specified. Offsets are" << endl; + cerr << " not cumulative. This option implies realtime mode (-R) and also enables a" << endl; + cerr << " high-consistency pitch shifting mode, appropriate for dynamic pitch changes." << endl; + cerr << " Because of the use of realtime mode, the overall duration will not be exact." << endl; + cerr << endl; + cerr << " --freqmap Use file F as the source for frequency map" << endl; + cerr << endl; + cerr << " As --pitchmap, except that the second column in the file contains frequency" << endl; + cerr << " multipliers rather than pitch offsets (the same as the difference between" << endl; + cerr << " pitch and frequency options above)." << endl; + cerr << endl; + cerr << "The following options provide a simple way to adjust the sound. See below" << endl; cerr << "for more details." << endl; cerr << endl; cerr << " -c, --crisp Crispness (N = 0,1,2,3,4,5,6); default 5 (see below)" << endl; - cerr << " -F, --formant Enable formant preservation when pitch shifting" << endl; + cerr << " -F, --formant Enable formant preservation when pitch shifting" << endl; cerr << endl; cerr << "The remaining options fine-tune the processing mode and stretch algorithm." << endl; cerr << "These are mostly included for test purposes; the default settings and standard" << endl; cerr << "crispness parameter are intended to provide the best sounding set of options" << endl; - cerr << "for most situations. The default is to use none of these options." << endl; + cerr << "for most situations. The default is to use none of these options." << endl; cerr << endl; cerr << " -L, --loose Relax timing in hope of better transient preservation" << endl; cerr << " -P, --precise Ignored: The opposite of -L, this is default from 1.6" << endl; @@ -248,6 +290,8 @@ int main(int argc, char **argv) cerr << " --pitch-hq In RT mode, use a slower, higher quality pitch shift" << endl; cerr << " --centre-focus Preserve focus of centre material in stereo" << endl; cerr << " (at a cost in width and individual channel quality)" << endl; + cerr << " --ignore-clipping Ignore clipping at output; the default is to restart" << endl; + cerr << " with reduced gain if clipping occurs" << endl; cerr << endl; cerr << " -d, --debug Select debug level (N = 0,1,2,3); default 0, full 3" << endl; cerr << " (N.B. debug level 3 includes audible ticks in output)" << endl; @@ -265,7 +309,7 @@ int main(int argc, char **argv) cerr << " -c 5 default processing options" << endl; cerr << " -c 6 equivalent to --no-lamination --window-short (may be good for drums)" << endl; cerr << endl; - return 2; + return 2; } if (ratio <= 0.0) { @@ -278,6 +322,12 @@ int main(int argc, char **argv) cerr << " provided -- crispness will override these other options" << endl; } + if (hqpitch && freqOrPitchMapSpecified) { + cerr << "WARNING: High-quality pitch mode selected, but frequency or pitch map file is" << endl; + cerr << " provided -- pitch mode will be overridden by high-consistency mode" << endl; + hqpitch = false; + } + switch (crispness) { case -1: crispness = 5; break; case 0: detector = CompoundDetector; transients = NoTransients; lamination = false; longwin = true; shortwin = false; break; @@ -303,34 +353,35 @@ int main(int argc, char **argv) cerr << ")" << endl; } - std::map mapping; - - if (mapfile != "") { - std::ifstream ifile(mapfile.c_str()); + std::map timeMap; + if (timeMapFile != "") { + std::ifstream ifile(timeMapFile.c_str()); if (!ifile.is_open()) { - cerr << "ERROR: Failed to open time map file \"" << mapfile << "\"" - << endl; + cerr << "ERROR: Failed to open time map file \"" + << timeMapFile << "\"" << endl; return 1; } std::string line; int lineno = 0; while (!ifile.eof()) { std::getline(ifile, line); - while (line.length() > 0 && line[0] == ' ') line = line.substr(1); + while (line.length() > 0 && line[0] == ' ') { + line = line.substr(1); + } if (line == "") { ++lineno; continue; } std::string::size_type i = line.find_first_of(" "); if (i == std::string::npos) { - cerr << "ERROR: Time map file \"" << mapfile + cerr << "ERROR: Time map file \"" << timeMapFile << "\" is malformed at line " << lineno << endl; return 1; } size_t source = atoi(line.substr(0, i).c_str()); while (i < line.length() && line[i] == ' ') ++i; size_t target = atoi(line.substr(i).c_str()); - mapping[source] = target; + timeMap[source] = target; if (debug > 0) { cerr << "adding mapping from " << source << " to " << target << endl; } @@ -339,7 +390,57 @@ int main(int argc, char **argv) ifile.close(); if (!quiet) { - cerr << "Read " << mapping.size() << " line(s) from map file" << endl; + cerr << "Read " << timeMap.size() << " line(s) from time map file" << endl; + } + } + + std::map freqMap; + + if (freqOrPitchMapSpecified) { + std::string file = freqMapFile; + bool convertFromPitch = false; + if (pitchMapFile != "") { + file = pitchMapFile; + convertFromPitch = true; + } + std::ifstream ifile(file.c_str()); + if (!ifile.is_open()) { + cerr << "ERROR: Failed to open map file \"" << file << "\"" << endl; + return 1; + } + std::string line; + int lineno = 0; + while (!ifile.eof()) { + std::getline(ifile, line); + while (line.length() > 0 && line[0] == ' ') { + line = line.substr(1); + } + if (line == "") { + ++lineno; + continue; + } + std::string::size_type i = line.find_first_of(" "); + if (i == std::string::npos) { + cerr << "ERROR: Map file \"" << file + << "\" is malformed at line " << lineno << endl; + return 1; + } + size_t source = atoi(line.substr(0, i).c_str()); + while (i < line.length() && line[i] == ' ') ++i; + double freq = atof(line.substr(i).c_str()); + if (convertFromPitch) { + freq = pow(2.0, freq / 12.0); + } + freqMap[source] = freq; + if (debug > 0) { + cerr << "adding mapping for source frame " << source << " of frequency multiplier " << freq << endl; + } + ++lineno; + } + ifile.close(); + + if (!quiet) { + cerr << "Read " << freqMap.size() << " line(s) from frequency map file" << endl; } } @@ -355,9 +456,9 @@ int main(int argc, char **argv) sndfile = sf_open(fileName, SFM_READ, &sfinfo); if (!sndfile) { - cerr << "ERROR: Failed to open input file \"" << fileName << "\": " - << sf_strerror(sndfile) << endl; - return 1; + cerr << "ERROR: Failed to open input file \"" << fileName << "\": " + << sf_strerror(sndfile) << endl; + return 1; } if (sfinfo.samplerate == 0) { @@ -383,9 +484,9 @@ int main(int argc, char **argv) sndfileOut = sf_open(fileNameOut, SFM_WRITE, &sfinfoOut) ; if (!sndfileOut) { - cerr << "ERROR: Failed to open output file \"" << fileNameOut << "\" for writing: " - << sf_strerror(sndfileOut) << endl; - return 1; + cerr << "ERROR: Failed to open output file \"" << fileNameOut << "\" for writing: " + << sf_strerror(sndfileOut) << endl; + return 1; } int ibs = 1024; @@ -402,6 +503,10 @@ int main(int argc, char **argv) if (hqpitch) options |= RubberBandStretcher::OptionPitchHighQuality; if (together) options |= RubberBandStretcher::OptionChannelsTogether; + if (freqOrPitchMapSpecified) { + options |= RubberBandStretcher::OptionPitchHighConsistency; + } + switch (threading) { case 0: options |= RubberBandStretcher::OptionThreadingAuto; @@ -439,46 +544,134 @@ int main(int argc, char **argv) } if (pitchshift != 0.0) { - frequencyshift *= pow(2.0, pitchshift / 12); + frequencyshift *= pow(2.0, pitchshift / 12.0); } cerr << "Using time ratio " << ratio; - cerr << " and frequency ratio " << frequencyshift << endl; + if (!freqOrPitchMapSpecified) { + cerr << " and frequency ratio " << frequencyshift << endl; + } else { + cerr << " and initial frequency ratio " << frequencyshift << endl; + } + #ifdef _WIN32 RubberBand:: #endif timeval tv; (void)gettimeofday(&tv, 0); - + RubberBandStretcher::setDefaultDebugLevel(debug); - RubberBandStretcher ts(sfinfo.samplerate, channels, options, - ratio, frequencyshift); + size_t countIn = 0, countOut = 0; - ts.setExpectedInputDuration(sfinfo.frames); + float gain = 1.f; + bool successful = false; - float *fbuf = new float[channels * ibs]; - float **ibuf = new float *[channels]; - for (size_t i = 0; i < channels; ++i) ibuf[i] = new float[ibs]; + while (!successful) { // we may have to repeat with a modified + // gain, if clipping occurs + successful = true; - int frame = 0; - int percent = 0; + RubberBandStretcher ts(sfinfo.samplerate, channels, options, + ratio, frequencyshift); + ts.setExpectedInputDuration(sfinfo.frames); - sf_seek(sndfile, 0, SEEK_SET); - - if (!realtime) { - - if (!quiet) { - cerr << "Pass 1: Studying..." << endl; + float *fbuf = new float[channels * ibs]; + float **ibuf = new float *[channels]; + for (size_t i = 0; i < channels; ++i) { + ibuf[i] = new float[ibs]; } + int frame = 0; + int percent = 0; + + sf_seek(sndfile, 0, SEEK_SET); + + if (!realtime) { + + if (!quiet) { + cerr << "Pass 1: Studying..." << endl; + } + + while (frame < sfinfo.frames) { + + int count = -1; + + if ((count = sf_readf_float(sndfile, fbuf, ibs)) <= 0) break; + + for (size_t c = 0; c < channels; ++c) { + for (int i = 0; i < count; ++i) { + float value = fbuf[i * channels + c]; + ibuf[c][i] = value; + } + } + + bool final = (frame + ibs >= sfinfo.frames); + + ts.study(ibuf, count, final); + + int p = int((double(frame) * 100.0) / sfinfo.frames); + if (p > percent || frame == 0) { + percent = p; + if (!quiet) { + cerr << "\r" << percent << "% "; + } + } + + frame += ibs; + } + + if (!quiet) { + cerr << "\rCalculating profile..." << endl; + } + + sf_seek(sndfile, 0, SEEK_SET); + } + + frame = 0; + percent = 0; + + if (!timeMap.empty()) { + ts.setKeyFrameMap(timeMap); + } + + std::map::const_iterator freqMapItr = freqMap.begin(); + + countIn = 0; + countOut = 0; + bool clipping = false; + while (frame < sfinfo.frames) { int count = -1; + int thisBlockSize = ibs; - if ((count = sf_readf_float(sndfile, fbuf, ibs)) <= 0) break; + while (freqMapItr != freqMap.end()) { + size_t nextFreqFrame = freqMapItr->first + ts.getLatency(); + if (nextFreqFrame <= countIn) { + double s = frequencyshift * freqMapItr->second; + if (debug > 0) { + cerr << "at frame " << countIn + << " (requested at " << freqMapItr->first + << " plus latency " << ts.getLatency() + << ") updating frequency ratio to " << s << endl; + } + ts.setPitchScale(s); + ++freqMapItr; + } else { + if (nextFreqFrame < countIn + thisBlockSize) { + thisBlockSize = nextFreqFrame - countIn; + } + break; + } + } + if ((count = sf_readf_float(sndfile, fbuf, thisBlockSize)) < 0) { + break; + } + + countIn += count; + for (size_t c = 0; c < channels; ++c) { for (int i = 0; i < count; ++i) { float value = fbuf[i * channels + c]; @@ -486,9 +679,70 @@ int main(int argc, char **argv) } } - bool final = (frame + ibs >= sfinfo.frames); + bool final = (frame + thisBlockSize >= sfinfo.frames); - ts.study(ibuf, count, final); + if (debug > 2) { + cerr << "count = " << count << ", ibs = " << thisBlockSize << ", frame = " << frame << ", frames = " << sfinfo.frames << ", final = " << final << endl; + } + + ts.process(ibuf, count, final); + + int avail = ts.available(); + if (debug > 1) cerr << "available = " << avail << endl; + + if (avail > 0) { + float **obf = new float *[channels]; + for (size_t i = 0; i < channels; ++i) { + obf[i] = new float[avail]; + } + ts.retrieve(obf, avail); + countOut += avail; + float *fobf = new float[channels * avail]; + for (size_t c = 0; c < channels; ++c) { + for (int i = 0; i < avail; ++i) { + float value = gain * obf[c][i]; + if (ignoreClipping) { // i.e. just clamp, don't bail out + if (value > 1.f) value = 1.f; + if (value < -1.f) value = -1.f; + } else { + if (value >= 1.f || value < -1.f) { + clipping = true; + gain = (0.999f / fabsf(obf[c][i])); + } + } + fobf[i * channels + c] = value; + } + } + sf_writef_float(sndfileOut, fobf, avail); + delete[] fobf; + for (size_t i = 0; i < channels; ++i) { + delete[] obf[i]; + } + delete[] obf; + } + + if (clipping) { + if (!quiet) { + cerr << "NOTE: Clipping detected at output sample " + << countOut << ", restarting with " + << "reduced gain of " << gain + << " (supply --ignore-clipping to avoid this)" << endl; + } + const float mingain = 0.75f; + if (gain < mingain) { + cerr << "WARNING: Clipped values were implausibly high: " + << "something wrong with input or process - " + << "not reducing gain below " << mingain << endl; + gain = mingain; + ignoreClipping = true; + } + successful = false; + break; + } + + if (frame == 0 && !realtime && !quiet) { + cerr << "Pass 2: Processing..." << endl; + } int p = int((double(frame) * 100.0) / sfinfo.frames); if (p > percent || frame == 0) { @@ -498,136 +752,55 @@ int main(int argc, char **argv) } } - frame += ibs; + frame += thisBlockSize; } + if (!successful) { + sf_seek(sndfile, 0, SEEK_SET); + sf_seek(sndfileOut, 0, SEEK_SET); + continue; + } + if (!quiet) { - cerr << "\rCalculating profile..." << endl; + cerr << "\r " << endl; } + int avail; - sf_seek(sndfile, 0, SEEK_SET); - } + while ((avail = ts.available()) >= 0) { - frame = 0; - percent = 0; - - if (!mapping.empty()) { - ts.setKeyFrameMap(mapping); + if (debug > 1) { + cerr << "(completing) available = " << avail << endl; + } + + if (avail > 0) { + float **obf = new float *[channels]; + for (size_t i = 0; i < channels; ++i) { + obf[i] = new float[avail]; + } + ts.retrieve(obf, avail); + countOut += avail; + float *fobf = new float[channels * avail]; + for (size_t c = 0; c < channels; ++c) { + for (int i = 0; i < avail; ++i) { + float value = gain * obf[c][i]; + if (value > 1.f) value = 1.f; + if (value < -1.f) value = -1.f; + fobf[i * channels + c] = value; + } + } + + sf_writef_float(sndfileOut, fobf, avail); + delete[] fobf; + for (size_t i = 0; i < channels; ++i) { + delete[] obf[i]; + } + delete[] obf; + } else { + usleep(10000); + } + } } - size_t countIn = 0, countOut = 0; - - while (frame < sfinfo.frames) { - - int count = -1; - - if ((count = sf_readf_float(sndfile, fbuf, ibs)) < 0) break; - - countIn += count; - - for (size_t c = 0; c < channels; ++c) { - for (int i = 0; i < count; ++i) { - float value = fbuf[i * channels + c]; - ibuf[c][i] = value; - } - } - - bool final = (frame + ibs >= sfinfo.frames); - - if (debug > 2) { - cerr << "count = " << count << ", ibs = " << ibs << ", frame = " << frame << ", frames = " << sfinfo.frames << ", final = " << final << endl; - } - - ts.process(ibuf, count, final); - - int avail = ts.available(); - if (debug > 1) cerr << "available = " << avail << endl; - - if (avail > 0) { - float **obf = new float *[channels]; - for (size_t i = 0; i < channels; ++i) { - obf[i] = new float[avail]; - } - ts.retrieve(obf, avail); - countOut += avail; - float *fobf = new float[channels * avail]; - for (size_t c = 0; c < channels; ++c) { - for (int i = 0; i < avail; ++i) { - float value = obf[c][i]; - if (value > 1.f) value = 1.f; - if (value < -1.f) value = -1.f; - fobf[i * channels + c] = value; - } - } -// cout << "fobf mean: "; -// double d = 0; -// for (int i = 0; i < avail; ++i) { -// d += fobf[i]; -// } -// d /= avail; -// cout << d << endl; - sf_writef_float(sndfileOut, fobf, avail); - delete[] fobf; - for (size_t i = 0; i < channels; ++i) { - delete[] obf[i]; - } - delete[] obf; - } - - if (frame == 0 && !realtime && !quiet) { - cerr << "Pass 2: Processing..." << endl; - } - - int p = int((double(frame) * 100.0) / sfinfo.frames); - if (p > percent || frame == 0) { - percent = p; - if (!quiet) { - cerr << "\r" << percent << "% "; - } - } - - frame += ibs; - } - - if (!quiet) { - cerr << "\r " << endl; - } - int avail; - - while ((avail = ts.available()) >= 0) { - - if (debug > 1) { - cerr << "(completing) available = " << avail << endl; - } - - if (avail > 0) { - float **obf = new float *[channels]; - for (size_t i = 0; i < channels; ++i) { - obf[i] = new float[avail]; - } - ts.retrieve(obf, avail); - countOut += avail; - float *fobf = new float[channels * avail]; - for (size_t c = 0; c < channels; ++c) { - for (int i = 0; i < avail; ++i) { - float value = obf[c][i]; - if (value > 1.f) value = 1.f; - if (value < -1.f) value = -1.f; - fobf[i * channels + c] = value; - } - } - - sf_writef_float(sndfileOut, fobf, avail); - delete[] fobf; - for (size_t i = 0; i < channels; ++i) { - delete[] obf[i]; - } - delete[] obf; - } else { - usleep(10000); - } - } - sf_close(sndfile); sf_close(sndfileOut); @@ -638,7 +811,7 @@ int main(int argc, char **argv) #ifdef _WIN32 RubberBand:: #endif - timeval etv; + timeval etv; (void)gettimeofday(&etv, 0); etv.tv_sec -= tv.tv_sec; @@ -649,10 +822,7 @@ int main(int argc, char **argv) etv.tv_usec -= tv.tv_usec; double sec = double(etv.tv_sec) + (double(etv.tv_usec) / 1000000.0); - cerr << "elapsed time: " << sec - << " sec, in frames/sec: " << int64_t(round(countIn/sec)) - << ", out frames/sec: " << int64_t(round(countOut/sec)) - << endl; + cerr << "elapsed time: " << sec << " sec, in frames/sec: " << countIn/sec << ", out frames/sec: " << countOut/sec << endl; } RubberBand::Profiler::dump(); From f6a66171bcb9254b8a88906530fddfdcd9f72cfb Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 10 May 2021 18:11:35 +0100 Subject: [PATCH 03/32] Initial experimental import of bq resampler --- meson.build | 8 +- meson_options.txt | 2 +- src/dsp/BQResampler.cpp | 633 ++++++++++++++++++++++++++++++++++++++++ src/dsp/BQResampler.h | 165 +++++++++++ src/dsp/Resampler.cpp | 418 ++++++++++++++++++-------- src/dsp/Resampler.h | 23 +- src/system/Allocators.h | 92 ++++++ src/system/VectorOps.h | 52 ++++ 8 files changed, 1269 insertions(+), 124 deletions(-) create mode 100644 src/dsp/BQResampler.cpp create mode 100644 src/dsp/BQResampler.h diff --git a/meson.build b/meson.build index 1585b51..65d49eb 100644 --- a/meson.build +++ b/meson.build @@ -199,7 +199,13 @@ else endif # fft -if resampler == 'libsamplerate' +if resampler == 'builtin' + config_summary += { 'Resampler': 'Built-in' } + message('For resampler: using built-in implementation') + library_sources += 'src/dsp/BQResampler.cpp' + feature_defines += ['-DUSE_BQRESAMPLER'] + +elif resampler == 'libsamplerate' if samplerate_dep.found() config_summary += { 'Resampler': 'libsamplerate' } message('For resampler: using libsamplerate') diff --git a/meson_options.txt b/meson_options.txt index 86bf686..8e65fa0 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -7,7 +7,7 @@ option('fft', option('resampler', type: 'combo', - choices: ['auto', 'libsamplerate', 'speex', 'ipp'], + choices: ['auto', 'builtin', 'libsamplerate', 'speex', 'ipp'], value: 'auto', description: 'Resampler library to use. Recommended is libsamplerate. The default (auto) will use libsamplerate if available, speex otherwise.') diff --git a/src/dsp/BQResampler.cpp b/src/dsp/BQResampler.cpp new file mode 100644 index 0000000..2a628f8 --- /dev/null +++ b/src/dsp/BQResampler.cpp @@ -0,0 +1,633 @@ +//* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band Library + An audio time-stretching and pitch-shifting library. + Copyright 2007-2021 Particular Programs Ltd. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. + + Alternatively, if you have a valid commercial licence for the + Rubber Band Library obtained by agreement with the copyright + holders, you may redistribute and/or modify it under the terms + described in that licence. + + If you wish to distribute code using the Rubber Band Library + under terms other than those of the GNU General Public License, + you must obtain a valid commercial licence before doing so. +*/ + +#include "BQResampler.h" + +#include + +#include + +#include "system/Allocators.h" +#include "system/VectorOps.h" + +#define BQ_R__ R__ + +using std::vector; +using std::cerr; +using std::endl; +using std::min; +using std::max; + +namespace RubberBand { + +BQResampler::BQResampler(Parameters parameters, int channels) : + m_qparams(parameters.quality), + m_dynamism(parameters.dynamism), + m_ratio_change(parameters.ratioChange), + m_debug_level(parameters.debugLevel), + m_initial_rate(parameters.referenceSampleRate), + m_channels(channels), + m_fade_count(0), + m_initialised(false) +{ + if (m_debug_level > 0) { + cerr << "BQResampler::BQResampler: " + << (m_dynamism == RatioOftenChanging ? "often-changing" : "mostly-fixed") + << ", " + << (m_ratio_change == SmoothRatioChange ? "smooth" : "sudden") + << " ratio changes, ref " << m_initial_rate << " Hz" << endl; + } + + if (m_dynamism == RatioOftenChanging) { + m_proto_length = m_qparams.proto_p * m_qparams.p_multiple + 1; + if (m_debug_level > 0) { + cerr << "BQResampler: creating prototype filter of length " + << m_proto_length << endl; + } + m_prototype = make_filter(m_proto_length, m_qparams.proto_p); + m_prototype.push_back(0.0); // interpolate without fear + } + + int phase_reserve = 2 * int(round(m_initial_rate)); + int buffer_reserve = 1000 * m_channels; + m_state_a.phase_info.reserve(phase_reserve); + m_state_a.buffer.reserve(buffer_reserve); + + if (m_dynamism == RatioOftenChanging) { + m_state_b.phase_info.reserve(phase_reserve); + m_state_b.buffer.reserve(buffer_reserve); + } + + m_s = &m_state_a; + m_fade = &m_state_b; +} + +BQResampler::BQResampler(const BQResampler &other) : + m_qparams(other.m_qparams), + m_dynamism(other.m_dynamism), + m_ratio_change(other.m_ratio_change), + m_debug_level(other.m_debug_level), + m_initial_rate(other.m_initial_rate), + m_channels(other.m_channels), + m_state_a(other.m_state_a), + m_state_b(other.m_state_b), + m_fade_count(other.m_fade_count), + m_prototype(other.m_prototype), + m_proto_length(other.m_proto_length), + m_initialised(other.m_initialised) +{ + if (other.m_s == &(other.m_state_a)) { + m_s = &m_state_a; + m_fade = &m_state_b; + } else { + m_s = &m_state_b; + m_fade = &m_state_a; + } +} + +void +BQResampler::reset() +{ + m_initialised = false; + m_fade_count = 0; +} + +BQResampler::QualityParams::QualityParams(Quality q) +{ + switch (q) { + case Fastest: + p_multiple = 12; + proto_p = 160; + k_snr = 70.0; + k_transition = 0.2; + cut = 0.9; + break; + case FastestTolerable: + p_multiple = 62; + proto_p = 160; + k_snr = 90.0; + k_transition = 0.05; + cut = 0.975; + break; + case Best: + p_multiple = 122; + proto_p = 800; + k_snr = 100.0; + k_transition = 0.01; + cut = 0.995; + break; + } +} + +int +BQResampler::resampleInterleaved(float *const out, + int outspace, + const float *const in, + int incount, + double ratio, + bool final) { + + int fade_length = round(m_initial_rate / 1000.0); + if (fade_length < 6) { + fade_length = 6; + } + int max_fade = min(outspace, int(floor(incount * ratio))) / 2; + if (fade_length > max_fade) { + fade_length = max_fade; + } + + if (!m_initialised) { + state_for_ratio(*m_s, ratio, *m_fade); + m_initialised = true; + } else if (ratio != m_s->parameters.ratio) { + state *tmp = m_fade; + m_fade = m_s; + m_s = tmp; + state_for_ratio(*m_s, ratio, *m_fade); + if (m_ratio_change == SmoothRatioChange) { + if (m_debug_level > 0) { + cerr << "BQResampler: ratio changed, beginning fade of length " + << fade_length << endl; + } + m_fade_count = fade_length; + } + } + + int i = 0, o = 0; + int bufsize = m_s->buffer.size(); + + int incount_samples = incount * m_channels; + int outspace_samples = outspace * m_channels; + + while (o < outspace_samples) { + while (i < incount_samples && m_s->fill < bufsize) { + m_s->buffer[m_s->fill++] = in[i++]; + } + if (m_s->fill == bufsize) { + out[o++] = reconstruct_one(m_s); + } else if (final && m_s->fill > m_s->centre) { + out[o++] = reconstruct_one(m_s); + } else if (final && m_s->fill == m_s->centre && + m_s->current_phase != m_s->initial_phase) { + out[o++] = reconstruct_one(m_s); + } else { + break; + } + } + + int fbufsize = m_fade->buffer.size(); + int fi = 0, fo = 0; + while (fo < o && m_fade_count > 0) { + while (fi < incount_samples && m_fade->fill < fbufsize) { + m_fade->buffer[m_fade->fill++] = in[fi++]; + } + if (m_fade->fill == fbufsize) { + double r = reconstruct_one(m_fade); + double fadeWith = out[fo]; + double extent = double(m_fade_count - 1) / double(fade_length); + double mixture = 0.5 * (1.0 - cos(M_PI * extent)); + double mixed = r * mixture + fadeWith * (1.0 - mixture); + out[fo] = mixed; + ++fo; + if (m_fade->current_channel == 0) { + --m_fade_count; + } + } else { + break; + } + } + + return o / m_channels; +} + +int +BQResampler::gcd(int a, int b) const +{ + int c = a % b; + if (c == 0) return b; + else return gcd(b, c); +} + +double +BQResampler::bessel0(double x) const +{ + static double facsquared[] = { + 0.0, 1.0, 4.0, 36.0, + 576.0, 14400.0, 518400.0, 25401600.0, + 1625702400.0, 131681894400.0, 1.316818944E13, 1.59335092224E15, + 2.29442532803E17, 3.87757880436E19, 7.60005445655E21, + 1.71001225272E24, 4.37763136697E26, 1.26513546506E29, + 4.09903890678E31, 1.47975304535E34 + }; + static int nterms = sizeof(facsquared) / sizeof(facsquared[0]); + double b = 1.0; + for (int n = 1; n < nterms; ++n) { + double ff = facsquared[n]; + double term = pow(x / 2.0, n * 2.0) / ff; + b += term; + } + return b; +} + +vector +BQResampler::kaiser(double beta, int len) const +{ + double denominator = bessel0(beta); + int half = (len % 2 == 0 ? len/2 : (len+1)/2); + vector v(len, 0.0); + for (int n = 0; n < half; ++n) { + double k = (2.0 * n) / (len-1) - 1.0; + v[n] = bessel0 (beta * sqrt(1.0 - k*k)) / denominator; + } + for (int n = half; n < len; ++n) { + v[n] = v[len-1 - n]; + } + return v; +} + +void +BQResampler::kaiser_params(double attenuation, + double transition, + double &beta, + int &len) const +{ + if (attenuation > 21.0) { + len = 1 + ceil((attenuation - 7.95) / (2.285 * transition)); + } else { + len = 1 + ceil(5.79 / transition); + } + beta = 0.0; + if (attenuation > 50.0) { + beta = 0.1102 * (attenuation - 8.7); + } else if (attenuation > 21.0) { + beta = 0.5842 * (pow (attenuation - 21.0, 0.4)) + + 0.07886 * (attenuation - 21.0); + } +} + +vector +BQResampler::kaiser_for(double attenuation, + double transition, + int minlen, + int maxlen) const +{ + double beta; + int m; + kaiser_params(attenuation, transition, beta, m); + int mb = m; + if (maxlen > 0 && mb > maxlen - 1) { + mb = maxlen - 1; + } else if (minlen > 0 && mb < minlen) { + mb = minlen; + } + if (mb % 2 == 0) ++mb; + if (m_debug_level > 0) { + cerr << "BQResampler: window attenuation " << attenuation + << ", transition " << transition + << " -> length " << m << " adjusted to " << mb + << ", beta " << beta << endl; + } + return kaiser(beta, mb); +} + +void +BQResampler::sinc_multiply(double peak_to_zero, vector &buf) const +{ + int len = int(buf.size()); + if (len < 2) return; + + int left = len / 2; + int right = (len + 1) / 2; + double m = M_PI / peak_to_zero; + + for (int i = 1; i <= right; ++i) { + double x = i * m; + double sinc = sin(x) / x; + if (i <= left) { + buf[left - i] *= sinc; + } + if (i < right) { + buf[i + left] *= sinc; + } + } +} + +BQResampler::params +BQResampler::fill_params(double ratio, int num, int denom) const +{ + params p; + int g = gcd (num, denom); + p.ratio = ratio; + p.numerator = num / g; + p.denominator = denom / g; + p.effective = double(p.numerator) / double(p.denominator); + p.peak_to_zero = max(p.denominator, p.numerator); + p.peak_to_zero /= m_qparams.cut; + p.scale = double(p.numerator) / double(p.peak_to_zero); + + if (m_debug_level > 0) { + cerr << "BQResampler: ratio " << p.ratio + << " -> fraction " << p.numerator << "/" << p.denominator + << " with error " << p.effective - p.ratio + << endl; + cerr << "BQResampler: peak-to-zero " << p.peak_to_zero + << ", scale " << p.scale + << endl; + } + + return p; +} + +BQResampler::params +BQResampler::pick_params(double ratio) const +{ + // Farey algorithm, see + // https://www.johndcook.com/blog/2010/10/20/best-rational-approximation/ + int max_denom = 192000; + double a = 0.0, b = 1.0, c = 1.0, d = 0.0; + double pa = a, pb = b, pc = c, pd = d; + double eps = 1e-9; + while (b <= max_denom && d <= max_denom) { + double mediant = (a + c) / (b + d); + if (fabs(ratio - mediant) < eps) { + if (b + d <= max_denom) { + return fill_params(ratio, a + c, b + d); + } else if (d > b) { + return fill_params(ratio, c, d); + } else { + return fill_params(ratio, a, b); + } + } + if (ratio > mediant) { + pa = a; pb = b; + a += c; b += d; + } else { + pc = c; pd = d; + c += a; d += b; + } + } + if (fabs(ratio - (pc / pd)) < fabs(ratio - (pa / pb))) { + return fill_params(ratio, pc, pd); + } else { + return fill_params(ratio, pa, pb); + } +} + +void +BQResampler::phase_data_for(vector &target_phase_data, + floatbuf &target_phase_sorted_filter, + int filter_length, + const vector *filter, + int initial_phase, + int input_spacing, + int output_spacing) const +{ + target_phase_data.clear(); + target_phase_data.reserve(input_spacing); + + for (int p = 0; p < input_spacing; ++p) { + int next_phase = p - output_spacing; + while (next_phase < 0) next_phase += input_spacing; + next_phase %= input_spacing; + double dspace = double(input_spacing); + int zip_length = ceil(double(filter_length - p) / dspace); + int drop = ceil(double(max(0, output_spacing - p)) / dspace); + phase_rec phase; + phase.next_phase = next_phase; + phase.drop = drop; + phase.length = zip_length; + phase.start_index = 0; // we fill this in below if needed + target_phase_data.push_back(phase); + } + + if (m_dynamism == RatioMostlyFixed) { + if (!filter) throw std::logic_error("filter required at phase_data_for in RatioMostlyFixed mode"); + target_phase_sorted_filter.clear(); + target_phase_sorted_filter.reserve(filter_length); + for (int p = initial_phase; ; ) { + phase_rec &phase = target_phase_data[p]; + phase.start_index = target_phase_sorted_filter.size(); + for (int i = 0; i < phase.length; ++i) { + target_phase_sorted_filter.push_back + ((*filter)[i * input_spacing + p]); + } + p = phase.next_phase; + if (p == initial_phase) { + break; + } + } + } +} + +vector +BQResampler::make_filter(int filter_length, double peak_to_zero) const +{ + vector filter; + filter.reserve(filter_length); + + vector kaiser = kaiser_for(m_qparams.k_snr, m_qparams.k_transition, + 1, filter_length); + int k_length = kaiser.size(); + + if (k_length == filter_length) { + sinc_multiply(peak_to_zero, kaiser); + return kaiser; + } else { + kaiser.push_back(0.0); + double m = double(k_length - 1) / double(filter_length - 1); + for (int i = 0; i < filter_length; ++i) { + double ix = i * m; + int iix = floor(ix); + double remainder = ix - iix; + double value = 0.0; + value += kaiser[iix] * (1.0 - remainder); + value += kaiser[iix+1] * remainder; + filter.push_back(value); + } + sinc_multiply(peak_to_zero, filter); + return filter; + } +} + +void +BQResampler::state_for_ratio(BQResampler::state &target_state, + double ratio, + const BQResampler::state &BQ_R__ prev_state) const +{ + params parameters = pick_params(ratio); + target_state.parameters = parameters; + + target_state.filter_length = + int(parameters.peak_to_zero * m_qparams.p_multiple + 1); + + if (target_state.filter_length % 2 == 0) { + ++target_state.filter_length; + } + + int half_length = target_state.filter_length / 2; // nb length is odd + int input_spacing = parameters.numerator; + int initial_phase = half_length % input_spacing; + + target_state.initial_phase = initial_phase; + target_state.current_phase = initial_phase; + + if (m_dynamism == RatioMostlyFixed) { + + if (m_debug_level > 0) { + cerr << "BQResampler: creating filter of length " + << target_state.filter_length << endl; + } + + vector filter = + make_filter(target_state.filter_length, parameters.peak_to_zero); + + phase_data_for(target_state.phase_info, + target_state.phase_sorted_filter, + target_state.filter_length, &filter, + target_state.initial_phase, + input_spacing, + parameters.denominator); + } else { + phase_data_for(target_state.phase_info, + target_state.phase_sorted_filter, + target_state.filter_length, 0, + target_state.initial_phase, + input_spacing, + parameters.denominator); + } + + int buffer_left = half_length / input_spacing; + int buffer_right = buffer_left + 1; + + int buffer_length = buffer_left + buffer_right; + buffer_length = max(buffer_length, + int(prev_state.buffer.size() / m_channels)); + + target_state.centre = buffer_length / 2; + target_state.left = target_state.centre - buffer_left; + target_state.fill = target_state.centre; + + buffer_length *= m_channels; + target_state.centre *= m_channels; + target_state.left *= m_channels; + target_state.fill *= m_channels; + + int n_phases = int(target_state.phase_info.size()); + + if (m_debug_level > 0) { + cerr << "BQResampler: " << m_channels << " channel(s) interleaved" + << ", buffer left " << buffer_left + << ", right " << buffer_right + << ", total " << buffer_length << endl; + + cerr << "BQResampler: input spacing " << input_spacing + << ", output spacing " << parameters.denominator + << ", initial phase " << initial_phase + << " of " << n_phases << endl; + } + + if (prev_state.buffer.size() > 0) { + if (int(prev_state.buffer.size()) == buffer_length) { + target_state.buffer = prev_state.buffer; + target_state.fill = prev_state.fill; + } else { + target_state.buffer = floatbuf(buffer_length, 0.0); + for (int i = 0; i < prev_state.fill; ++i) { + int offset = i - prev_state.centre; + int new_ix = offset + target_state.centre; + if (new_ix >= 0 && new_ix < buffer_length) { + target_state.buffer[new_ix] = prev_state.buffer[i]; + target_state.fill = new_ix + 1; + } + } + } + + int phases_then = int(prev_state.phase_info.size()); + double distance_through = + double(prev_state.current_phase) / double(phases_then); + target_state.current_phase = round(n_phases * distance_through); + if (target_state.current_phase >= n_phases) { + target_state.current_phase = n_phases - 1; + } + } else { + target_state.buffer = floatbuf(buffer_length, 0.0); + } +} + +double +BQResampler::reconstruct_one(state *s) const +{ + const phase_rec &pr = s->phase_info[s->current_phase]; + int phase_length = pr.length; + double result = 0.0; + + if (m_dynamism == RatioMostlyFixed) { + int phase_start = pr.start_index; + if (m_channels == 1) { + result = v_multiply_and_sum + (s->phase_sorted_filter.data() + phase_start, + s->buffer.data() + s->left, + phase_length); + } else { + for (int i = 0; i < phase_length; ++i) { + result += + s->phase_sorted_filter[phase_start + i] * + s->buffer[s->left + i * m_channels + s->current_channel]; + } + } + } else { + double m = double(m_proto_length - 1) / double(s->filter_length - 1); + for (int i = 0; i < phase_length; ++i) { + double sample = + s->buffer[s->left + i * m_channels + s->current_channel]; + int filter_index = i * s->parameters.numerator + s->current_phase; + double proto_index = m * filter_index; + int iix = floor(proto_index); + double remainder = proto_index - iix; + double filter_value = m_prototype[iix] * (1.0 - remainder); + filter_value += m_prototype[iix+1] * remainder; + result += filter_value * sample; + } + } + + s->current_channel = (s->current_channel + 1) % m_channels; + + if (s->current_channel == 0) { + + if (pr.drop > 0) { + int drop = pr.drop * m_channels; + v_move(s->buffer.data(), s->buffer.data() + drop, + int(s->buffer.size()) - drop); + for (int i = 1; i <= drop; ++i) { + s->buffer[s->buffer.size() - i] = 0.0; + } + s->fill -= drop; + } + + s->current_phase = pr.next_phase; + } + + return result * s->parameters.scale; +} + +} diff --git a/src/dsp/BQResampler.h b/src/dsp/BQResampler.h new file mode 100644 index 0000000..c05af1d --- /dev/null +++ b/src/dsp/BQResampler.h @@ -0,0 +1,165 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band Library + An audio time-stretching and pitch-shifting library. + Copyright 2007-2021 Particular Programs Ltd. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. + + Alternatively, if you have a valid commercial licence for the + Rubber Band Library obtained by agreement with the copyright + holders, you may redistribute and/or modify it under the terms + described in that licence. + + If you wish to distribute code using the Rubber Band Library + under terms other than those of the GNU General Public License, + you must obtain a valid commercial licence before doing so. +*/ + +#ifndef BQ_BQRESAMPLER_H +#define BQ_BQRESAMPLER_H + +#include + +#include "system/Allocators.h" +#include "system/VectorOps.h" + +namespace RubberBand { + +class BQResampler +{ +public: + enum Quality { Best, FastestTolerable, Fastest }; + enum Dynamism { RatioOftenChanging, RatioMostlyFixed }; + enum RatioChange { SmoothRatioChange, SuddenRatioChange }; + + struct Parameters { + Quality quality; + Dynamism dynamism; + RatioChange ratioChange; + double referenceSampleRate; + int debugLevel; + + Parameters() : + quality(FastestTolerable), + dynamism(RatioMostlyFixed), + ratioChange(SmoothRatioChange), + referenceSampleRate(44100), + debugLevel(0) { } + }; + + BQResampler(Parameters parameters, int channels); + BQResampler(const BQResampler &); + + int resampleInterleaved(float *const out, int outspace, + const float *const in, int incount, + double ratio, bool final); + + void reset(); + +private: + struct QualityParams { + int p_multiple; + int proto_p; + double k_snr; + double k_transition; + double cut; + QualityParams(Quality); + }; + + const QualityParams m_qparams; + const Dynamism m_dynamism; + const RatioChange m_ratio_change; + const int m_debug_level; + const double m_initial_rate; + const int m_channels; + + struct params { + double ratio; + int numerator; + int denominator; + double effective; + double peak_to_zero; + double scale; + params() : ratio(1.0), numerator(1), denominator(1), + effective(1.0), peak_to_zero(0), scale(1.0) { } + }; + + struct phase_rec { + int next_phase; + int length; + int start_index; + int drop; + phase_rec() : next_phase(0), length(0), start_index(0), drop(0) { } + }; + + typedef std::vector > floatbuf; + + struct state { + params parameters; + int initial_phase; + int current_phase; + int current_channel; + int filter_length; + std::vector phase_info; + floatbuf phase_sorted_filter; + floatbuf buffer; + int left; + int centre; + int fill; + state() : initial_phase(0), current_phase(0), current_channel(0), + filter_length(0), left(0), centre(0), fill(0) { } + }; + + state m_state_a; + state m_state_b; + + state *m_s; // points at either m_state_a or m_state_b + state *m_fade; // whichever one m_s does not point to + + int m_fade_count; + + std::vector m_prototype; + int m_proto_length; + bool m_initialised; + + int gcd(int a, int b) const; + double bessel0(double x) const; + std::vector kaiser(double beta, int len) const; + void kaiser_params(double attenuation, double transition, + double &beta, int &len) const; + std::vector kaiser_for(double attenuation, double transition, + int minlen, int maxlen) const; + void sinc_multiply(double peak_to_zero, std::vector &buf) const; + + params fill_params(double ratio, int num, int denom) const; + params pick_params(double ratio) const; + + std::vector make_filter(int filter_length, + double peak_to_zero) const; + + void phase_data_for(std::vector &target_phase_data, + floatbuf &target_phase_sorted_filter, + int filter_length, + const std::vector *filter, + int initial_phase, + int input_spacing, + int output_spacing) const; + + void state_for_ratio(state &target_state, + double ratio, + const state &R__ prev_state) const; + + double reconstruct_one(state *s) const; + + BQResampler &operator=(const BQResampler &); // not provided +}; + +} + +#endif diff --git a/src/dsp/Resampler.cpp b/src/dsp/Resampler.cpp index 06199a4..dda917d 100644 --- a/src/dsp/Resampler.cpp +++ b/src/dsp/Resampler.cpp @@ -1,4 +1,4 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ /* Rubber Band Library @@ -22,7 +22,9 @@ */ #include "Resampler.h" -#include "base/Profiler.h" + +#include "system/Allocators.h" +#include "system/VectorOps.h" #include #include @@ -30,9 +32,6 @@ #include #include -#include "system/Allocators.h" -#include "system/VectorOps.h" - #ifdef HAVE_IPP #include #if (IPP_VERSION_MAJOR < 7) @@ -42,6 +41,10 @@ #endif #endif +#ifdef HAVE_SAMPLERATE +#define HAVE_LIBSAMPLERATE 1 +#endif + #ifdef HAVE_LIBSAMPLERATE #include #endif @@ -51,18 +54,26 @@ #endif #ifdef USE_SPEEX -#include "speex/speex_resampler.h" +#include "../speex/speex_resampler.h" +#endif + +#ifdef USE_BQRESAMPLER +#include "BQResampler.h" #endif #ifndef HAVE_IPP #ifndef HAVE_LIBSAMPLERATE #ifndef HAVE_LIBRESAMPLE #ifndef USE_SPEEX +#ifndef USE_BQRESAMPLER #error No resampler implementation selected! #endif #endif #endif #endif +#endif + +#define BQ_R__ R__ using namespace std; @@ -73,16 +84,16 @@ class Resampler::Impl public: virtual ~Impl() { } - virtual int resample(float *const R__ *const R__ out, + virtual int resample(float *const BQ_R__ *const BQ_R__ out, int outcount, - const float *const R__ *const R__ in, + const float *const BQ_R__ *const BQ_R__ in, int incount, double ratio, bool final) = 0; - virtual int resampleInterleaved(float *const R__ out, + virtual int resampleInterleaved(float *const BQ_R__ out, int outcount, - const float *const R__ in, + const float *const BQ_R__ in, int incount, double ratio, bool final) = 0; @@ -99,20 +110,21 @@ namespace Resamplers { class D_IPP : public Resampler::Impl { public: - D_IPP(Resampler::Quality quality, int channels, double initialSampleRate, + D_IPP(Resampler::Quality quality, Resampler::RatioChange, + int channels, double initialSampleRate, int maxBufferSize, int debugLevel); ~D_IPP(); - int resample(float *const R__ *const R__ out, + int resample(float *const BQ_R__ *const BQ_R__ out, int outcount, - const float *const R__ *const R__ in, + const float *const BQ_R__ *const BQ_R__ in, int incount, double ratio, bool final); - int resampleInterleaved(float *const R__ out, + int resampleInterleaved(float *const BQ_R__ out, int outcount, - const float *const R__ in, + const float *const BQ_R__ in, int incount, double ratio, bool final = false); @@ -144,6 +156,7 @@ protected: }; D_IPP::D_IPP(Resampler::Quality /* quality */, + Resampler::RatioChange /* ratioChange */, int channels, double initialSampleRate, int maxBufferSize, int debugLevel) : m_state(0), @@ -152,7 +165,7 @@ D_IPP::D_IPP(Resampler::Quality /* quality */, m_debugLevel(debugLevel) { if (m_debugLevel > 0) { - cerr << "Resampler::Resampler: using IPP implementation" << endl; + cerr << "Resampler::Resampler: using implementation: IPP" << endl; } m_window = 32; @@ -190,7 +203,7 @@ D_IPP::D_IPP(Resampler::Quality /* quality */, setBufSize(maxBufferSize + m_history); if (m_debugLevel > 1) { - cerr << "D_IPP: bufsize = " << m_bufsize << ", window = " << m_window << ", nStep = " << nStep << ", history = " << m_history << endl; + cerr << "bufsize = " << m_bufsize << ", window = " << m_window << ", nStep = " << nStep << ", history = " << m_history << endl; } int specSize = 0; @@ -214,18 +227,13 @@ D_IPP::D_IPP(Resampler::Quality /* quality */, 9.0f, m_state[c], hint); - - if (m_debugLevel > 1) { - cerr << "D_IPP: Resampler state size = " << specSize << ", allocated at " - << m_state[c] << endl; - } m_lastread[c] = m_history; m_time[c] = m_history; } if (m_debugLevel > 1) { - cerr << "D_IPP: Resampler init done" << endl; + cerr << "Resampler init done" << endl; } } @@ -248,9 +256,9 @@ D_IPP::setBufSize(int sz) { if (m_debugLevel > 1) { if (m_bufsize > 0) { - cerr << "D_IPP: resize bufsize " << m_bufsize << " -> "; + cerr << "resize bufsize " << m_bufsize << " -> "; } else { - cerr << "D_IPP: initialise bufsize to "; + cerr << "initialise bufsize to "; } } @@ -263,13 +271,13 @@ D_IPP::setBufSize(int sz) int n1 = m_bufsize + m_history + 2; if (m_debugLevel > 1) { - cerr << "D_IPP: inbuf allocating " << m_bufsize << " + " << m_history << " + 2 = " << n1 << endl; + cerr << "inbuf allocating " << m_bufsize << " + " << m_history << " + 2 = " << n1 << endl; } int n2 = (int)lrintf(ceil((m_bufsize - m_history) * m_factor + 2)); if (m_debugLevel > 1) { - cerr << "D_IPP: outbuf allocating (" << m_bufsize << " - " << m_history << ") * " << m_factor << " + 2 = " << n2 << endl; + cerr << "outbuf allocating (" << m_bufsize << " - " << m_history << ") * " << m_factor << " + 2 = " << n2 << endl; } m_inbuf = reallocate_and_zero_extend_channels @@ -277,30 +285,15 @@ D_IPP::setBufSize(int sz) m_outbuf = reallocate_and_zero_extend_channels (m_outbuf, m_channels, m_outbufsz, m_channels, n2); - + m_inbufsz = n1; m_outbufsz = n2; - - if (m_debugLevel > 2) { - - cerr << "D_IPP: inbuf ptr = " << m_inbuf << ", channel inbufs "; - for (int c = 0; c < m_channels; ++c) { - cerr << m_inbuf[c] << " "; - } - cerr << "at " << m_inbufsz * sizeof(float) << " bytes each" << endl; - - cerr << "D_IPP: outbuf ptr = " << m_outbuf << ", channel outbufs "; - for (int c = 0; c < m_channels; ++c) { - cerr << m_outbuf[c] << " "; - } - cerr << "at " << m_outbufsz * sizeof(float) << " bytes each" << endl; - } } int -D_IPP::resample(float *const R__ *const R__ out, +D_IPP::resample(float *const BQ_R__ *const BQ_R__ out, int outspace, - const float *const R__ *const R__ in, + const float *const BQ_R__ *const BQ_R__ in, int incount, double ratio, bool final) @@ -311,7 +304,7 @@ D_IPP::resample(float *const R__ *const R__ out, } if (m_debugLevel > 2) { - cerr << "D_IPP: incount = " << incount << ", ratio = " << ratio << ", est space = " << lrintf(ceil(incount * ratio)) << ", outspace = " << outspace << ", final = " << final << endl; + cerr << "incount = " << incount << ", ratio = " << ratio << ", est space = " << lrintf(ceil(incount * ratio)) << ", outspace = " << outspace << ", final = " << final << endl; } for (int c = 0; c < m_channels; ++c) { @@ -328,7 +321,7 @@ D_IPP::resample(float *const R__ *const R__ out, } if (m_debugLevel > 2) { - cerr << "D_IPP: lastread advanced to " << m_lastread[0] << endl; + cerr << "lastread advanced to " << m_lastread[0] << endl; } int got = doResample(outspace, ratio, final); @@ -341,9 +334,9 @@ D_IPP::resample(float *const R__ *const R__ out, } int -D_IPP::resampleInterleaved(float *const R__ out, +D_IPP::resampleInterleaved(float *const BQ_R__ out, int outspace, - const float *const R__ in, + const float *const BQ_R__ in, int incount, double ratio, bool final) @@ -354,7 +347,7 @@ D_IPP::resampleInterleaved(float *const R__ out, } if (m_debugLevel > 2) { - cerr << "D_IPP: incount = " << incount << ", ratio = " << ratio << ", est space = " << lrintf(ceil(incount * ratio)) << ", outspace = " << outspace << ", final = " << final << endl; + cerr << "incount = " << incount << ", ratio = " << ratio << ", est space = " << lrintf(ceil(incount * ratio)) << ", outspace = " << outspace << ", final = " << final << endl; } for (int c = 0; c < m_channels; ++c) { @@ -371,7 +364,7 @@ D_IPP::resampleInterleaved(float *const R__ out, } if (m_debugLevel > 2) { - cerr << "D_IPP: lastread advanced to " << m_lastread[0] << " after injection of " + cerr << "lastread advanced to " << m_lastread[0] << " after injection of " << incount << " samples" << endl; } @@ -392,20 +385,20 @@ D_IPP::doResample(int outspace, double ratio, bool final) int n = m_lastread[c] - m_history - int(m_time[c]); if (c == 0 && m_debugLevel > 2) { - cerr << "D_IPP: at start, lastread = " << m_lastread[c] << ", history = " + cerr << "at start, lastread = " << m_lastread[c] << ", history = " << m_history << ", time = " << m_time[c] << ", therefore n = " << n << endl; } if (n <= 0) { if (c == 0 && m_debugLevel > 1) { - cerr << "D_IPP: not enough input samples to do anything" << endl; + cerr << "not enough input samples to do anything" << endl; } continue; } if (c == 0 && m_debugLevel > 2) { - cerr << "D_IPP: before resample call, time = " << m_time[c] << endl; + cerr << "before resample call, time = " << m_time[c] << endl; } // We're committed to not overrunning outspace, so we need to @@ -414,7 +407,7 @@ D_IPP::doResample(int outspace, double ratio, bool final) int limit = int(floor(outspace / ratio)); if (n > limit) { if (c == 0 && m_debugLevel > 1) { - cerr << "D_IPP: trimming input samples from " << n << " to " << limit + cerr << "trimming input samples from " << n << " to " << limit << " to avoid overrunning " << outspace << " at output" << endl; } @@ -431,26 +424,26 @@ D_IPP::doResample(int outspace, double ratio, bool final) m_state[c]); int t = int(floor(m_time[c])); - + int moveFrom = t - m_history; - + if (c == 0 && m_debugLevel > 2) { - cerr << "D_IPP: converted " << n << " samples to " << outcount + cerr << "converted " << n << " samples to " << outcount << " (nb outbufsz = " << m_outbufsz << "), time advanced to " << m_time[c] << endl; - cerr << "D_IPP: rounding time to " << t << ", lastread = " + cerr << "rounding time to " << t << ", lastread = " << m_lastread[c] << ", history = " << m_history << endl; - cerr << "D_IPP: will move " << m_lastread[c] - moveFrom + cerr << "will move " << m_lastread[c] - moveFrom << " unconverted samples back from index " << moveFrom << " to 0" << endl; } - + if (moveFrom >= m_lastread[c]) { moveFrom = m_lastread[c]; if (c == 0 && m_debugLevel > 2) { - cerr << "D_IPP: number of samples to move is <= 0, " + cerr << "number of samples to move is <= 0, " << "not actually moving any" << endl; } } else { @@ -464,7 +457,7 @@ D_IPP::doResample(int outspace, double ratio, bool final) m_time[c] -= moveFrom; if (c == 0 && m_debugLevel > 2) { - cerr << "D_IPP: lastread reduced to " << m_lastread[c] + cerr << "lastread reduced to " << m_lastread[c] << ", time reduced to " << m_time[c] << endl; } @@ -483,7 +476,7 @@ D_IPP::doResample(int outspace, double ratio, bool final) int additionalcount = 0; if (c == 0 && m_debugLevel > 2) { - cerr << "D_IPP: final call, padding input with " << m_history + cerr << "final call, padding input with " << m_history << " zeros (symmetrical with m_history)" << endl; } @@ -492,14 +485,14 @@ D_IPP::doResample(int outspace, double ratio, bool final) } if (c == 0 && m_debugLevel > 2) { - cerr << "D_IPP: before resample call, time = " << m_time[c] << endl; + cerr << "before resample call, time = " << m_time[c] << endl; } int nAdditional = m_lastread[c] - int(m_time[c]); if (n + nAdditional > limit) { if (c == 0 && m_debugLevel > 1) { - cerr << "D_IPP: trimming final input samples from " << nAdditional + cerr << "trimming final input samples from " << nAdditional << " to " << (limit - n) << " to avoid overrunning " << outspace << " at output" << endl; @@ -517,9 +510,9 @@ D_IPP::doResample(int outspace, double ratio, bool final) m_state[c]); if (c == 0 && m_debugLevel > 2) { - cerr << "D_IPP: converted " << n << " samples to " << additionalcount + cerr << "converted " << n << " samples to " << additionalcount << ", time advanced to " << m_time[c] << endl; - cerr << "D_IPP: outcount = " << outcount << ", additionalcount = " << additionalcount << ", sum " << outcount + additionalcount << endl; + cerr << "outcount = " << outcount << ", additionalcount = " << additionalcount << ", sum " << outcount + additionalcount << endl; } if (c == 0) { @@ -529,7 +522,7 @@ D_IPP::doResample(int outspace, double ratio, bool final) } if (m_debugLevel > 2) { - cerr << "D_IPP: returning " << outcount << " samples" << endl; + cerr << "returning " << outcount << " samples" << endl; } return outcount; @@ -548,20 +541,21 @@ D_IPP::reset() class D_SRC : public Resampler::Impl { public: - D_SRC(Resampler::Quality quality, int channels, double initialSampleRate, + D_SRC(Resampler::Quality quality, Resampler::RatioChange ratioChange, + int channels, double initialSampleRate, int maxBufferSize, int m_debugLevel); ~D_SRC(); - int resample(float *const R__ *const R__ out, + int resample(float *const BQ_R__ *const BQ_R__ out, int outcount, - const float *const R__ *const R__ in, + const float *const BQ_R__ *const BQ_R__ in, int incount, double ratio, bool final); - int resampleInterleaved(float *const R__ out, + int resampleInterleaved(float *const BQ_R__ out, int outcount, - const float *const R__ in, + const float *const BQ_R__ in, int incount, double ratio, bool final = false); @@ -579,11 +573,12 @@ protected: int m_ioutsize; double m_prevRatio; bool m_ratioUnset; + bool m_smoothRatios; int m_debugLevel; }; -D_SRC::D_SRC(Resampler::Quality quality, int channels, double, - int maxBufferSize, int debugLevel) : +D_SRC::D_SRC(Resampler::Quality quality, Resampler::RatioChange ratioChange, + int channels, double, int maxBufferSize, int debugLevel) : m_src(0), m_iin(0), m_iout(0), @@ -592,25 +587,41 @@ D_SRC::D_SRC(Resampler::Quality quality, int channels, double, m_ioutsize(0), m_prevRatio(1.0), m_ratioUnset(true), + m_smoothRatios(ratioChange == Resampler::SmoothRatioChange), m_debugLevel(debugLevel) { if (m_debugLevel > 0) { - cerr << "Resampler::Resampler: using libsamplerate implementation" - << endl; + cerr << "Resampler::Resampler: using implementation: libsamplerate" + << endl; } + if (channels < 1) { + cerr << "Resampler::Resampler: unable to create resampler: invalid channel count " << channels << " supplied" << endl; +#ifdef NO_EXCEPTIONS + throw Resampler::ImplementationError; +#endif + return; + } + int err = 0; m_src = src_new(quality == Resampler::Best ? SRC_SINC_BEST_QUALITY : - quality == Resampler::Fastest ? SRC_LINEAR : - SRC_SINC_FASTEST, + quality == Resampler::Fastest ? SRC_SINC_FASTEST : + SRC_SINC_MEDIUM_QUALITY, channels, &err); if (err) { cerr << "Resampler::Resampler: failed to create libsamplerate resampler: " - << src_strerror(err) << endl; + << src_strerror(err) << endl; #ifndef NO_EXCEPTIONS throw Resampler::ImplementationError; #endif + return; + } else if (!m_src) { + cerr << "Resampler::Resampler: failed to create libsamplerate resampler, but no error reported?" << endl; +#ifndef NO_EXCEPTIONS + throw Resampler::ImplementationError; +#endif + return; } if (maxBufferSize > 0 && m_channels > 1) { @@ -631,9 +642,9 @@ D_SRC::~D_SRC() } int -D_SRC::resample(float *const R__ *const R__ out, +D_SRC::resample(float *const BQ_R__ *const BQ_R__ out, int outcount, - const float *const R__ *const R__ in, + const float *const BQ_R__ *const BQ_R__ in, int incount, double ratio, bool final) @@ -661,15 +672,15 @@ D_SRC::resample(float *const R__ *const R__ out, } int -D_SRC::resampleInterleaved(float *const R__ out, +D_SRC::resampleInterleaved(float *const BQ_R__ out, int outcount, - const float *const R__ in, + const float *const BQ_R__ in, int incount, double ratio, bool final) { SRC_DATA data; - + // libsamplerate smooths the filter change over the duration of // the processing block to avoid artifacts due to sudden changes, // and it uses outcount to determine how long to smooth the change @@ -682,7 +693,7 @@ D_SRC::resampleInterleaved(float *const R__ out, outcount = int(ceil(incount * ratio) + 5); } - if (m_ratioUnset) { + if (m_ratioUnset || !m_smoothRatios) { // The first time we set a ratio, we want to do it directly src_set_ratio(m_src, ratio); @@ -724,10 +735,9 @@ D_SRC::resampleInterleaved(float *const R__ out, data.input_frames = incount; data.output_frames = outcount; - data.src_ratio = ratio; data.end_of_input = (final ? 1 : 0); - + int err = src_process(m_src, &data); if (err) { @@ -737,7 +747,7 @@ D_SRC::resampleInterleaved(float *const R__ out, throw Resampler::ImplementationError; #endif } - + return (int)data.output_frames_gen; } @@ -755,20 +765,21 @@ D_SRC::reset() class D_Resample : public Resampler::Impl { public: - D_Resample(Resampler::Quality quality, int channels, double initialSampleRate, + D_Resample(Resampler::Quality quality, Resampler::RatioChange, + int channels, double initialSampleRate, int maxBufferSize, int m_debugLevel); ~D_Resample(); - int resample(float *const R__ *const R__ out, + int resample(float *const BQ_R__ *const BQ_R__ out, int outcount, - const float *const R__ *const R__ in, + const float *const BQ_R__ *const BQ_R__ in, int incount, double ratio, bool final); - int resampleInterleaved(float *const R__ out, + int resampleInterleaved(float *const BQ_R__ out, int outcount, - const float *const R__ in, + const float *const BQ_R__ in, int incount, double ratio, bool final); @@ -799,7 +810,7 @@ D_Resample::D_Resample(Resampler::Quality quality, m_debugLevel(debugLevel) { if (m_debugLevel > 0) { - cerr << "Resampler::Resampler: using libresample implementation" + cerr << "Resampler::Resampler: using implementation: libresample" << endl; } @@ -836,9 +847,9 @@ D_Resample::~D_Resample() } int -D_Resample::resample(float *const R__ *const R__ out, +D_Resample::resample(float *const BQ_R__ *const BQ_R__ out, int outcount, - const float *const R__ *const R__ in, + const float *const BQ_R__ *const BQ_R__ in, int incount, double ratio, bool final) @@ -895,9 +906,9 @@ D_Resample::resample(float *const R__ *const R__ out, } int -D_Resample::resampleInterleaved(float *const R__ out, +D_Resample::resampleInterleaved(float *const BQ_R__ out, int outcount, - const float *const R__ in, + const float *const BQ_R__ in, int incount, double ratio, bool final) @@ -937,25 +948,174 @@ D_Resample::reset() #endif /* HAVE_LIBRESAMPLE */ +#ifdef USE_BQRESAMPLER + +class D_BQResampler : public Resampler::Impl +{ +public: + D_BQResampler(Resampler::Parameters params, int channels); + ~D_BQResampler(); + + int resample(float *const BQ_R__ *const BQ_R__ out, + int outcount, + const float *const BQ_R__ *const BQ_R__ in, + int incount, + double ratio, + bool final); + + int resampleInterleaved(float *const BQ_R__ out, + int outcount, + const float *const BQ_R__ in, + int incount, + double ratio, + bool final = false); + + int getChannelCount() const { return m_channels; } + + void reset(); + +protected: + BQResampler *m_resampler; + float *m_iin; + float *m_iout; + int m_channels; + int m_iinsize; + int m_ioutsize; + int m_debugLevel; +}; + +D_BQResampler::D_BQResampler(Resampler::Parameters params, int channels) : + m_resampler(0), + m_iin(0), + m_iout(0), + m_channels(channels), + m_iinsize(0), + m_ioutsize(0), + m_debugLevel(params.debugLevel) +{ + if (m_debugLevel > 0) { + cerr << "Resampler::Resampler: using implementation: BQResampler" << endl; + } + + BQResampler::Parameters rparams; + switch (params.quality) { + case Resampler::Best: + rparams.quality = BQResampler::Best; + break; + case Resampler::FastestTolerable: + rparams.quality = BQResampler::FastestTolerable; + break; + case Resampler::Fastest: + rparams.quality = BQResampler::Fastest; + break; + } + switch (params.dynamism) { + case Resampler::RatioOftenChanging: + rparams.dynamism = BQResampler::RatioOftenChanging; + break; + case Resampler::RatioMostlyFixed: + rparams.dynamism = BQResampler::RatioMostlyFixed; + break; + } + switch (params.ratioChange) { + case Resampler::SmoothRatioChange: + rparams.ratioChange = BQResampler::SmoothRatioChange; + break; + case Resampler::SuddenRatioChange: + rparams.ratioChange = BQResampler::SuddenRatioChange; + break; + } + rparams.referenceSampleRate = params.initialSampleRate; + rparams.debugLevel = params.debugLevel; + + m_resampler = new BQResampler(rparams, m_channels); + + if (params.maxBufferSize > 0 && m_channels > 1) { + m_iinsize = params.maxBufferSize * m_channels; + m_ioutsize = params.maxBufferSize * m_channels * 2; + m_iin = allocate(m_iinsize); + m_iout = allocate(m_ioutsize); + } +} + +D_BQResampler::~D_BQResampler() +{ + delete m_resampler; + deallocate(m_iin); + deallocate(m_iout); +} + +int +D_BQResampler::resample(float *const BQ_R__ *const BQ_R__ out, + int outcount, + const float *const BQ_R__ *const BQ_R__ in, + int incount, + double ratio, + bool final) +{ + if (m_channels == 1) { + return resampleInterleaved(*out, outcount, *in, incount, ratio, final); + } + + if (incount * m_channels > m_iinsize) { + m_iin = reallocate(m_iin, m_iinsize, incount * m_channels); + m_iinsize = incount * m_channels; + } + if (outcount * m_channels > m_ioutsize) { + m_iout = reallocate(m_iout, m_ioutsize, outcount * m_channels); + m_ioutsize = outcount * m_channels; + } + + v_interleave(m_iin, in, m_channels, incount); + + int n = resampleInterleaved(m_iout, outcount, m_iin, incount, ratio, final); + + v_deinterleave(out, m_iout, m_channels, n); + + return n; +} + +int +D_BQResampler::resampleInterleaved(float *const BQ_R__ out, + int outcount, + const float *const BQ_R__ in, + int incount, + double ratio, + bool final) +{ + return m_resampler->resampleInterleaved(out, outcount, + in, incount, + ratio, final); +} + +void +D_BQResampler::reset() +{ + m_resampler->reset(); +} + +#endif /* USE_BQRESAMPLER */ + #ifdef USE_SPEEX class D_Speex : public Resampler::Impl { public: - D_Speex(Resampler::Quality quality, int channels, double initialSampleRate, + D_Speex(Resampler::Quality quality, Resampler::RatioChange, + int channels, double initialSampleRate, int maxBufferSize, int debugLevel); ~D_Speex(); - int resample(float *const R__ *const R__ out, + int resample(float *const BQ_R__ *const BQ_R__ out, int outcount, - const float *const R__ *const R__ in, + const float *const BQ_R__ *const BQ_R__ in, int incount, double ratio, bool final); - int resampleInterleaved(float *const R__ out, + int resampleInterleaved(float *const BQ_R__ out, int outcount, - const float *const R__ in, + const float *const BQ_R__ in, int incount, double ratio, bool final = false); @@ -982,7 +1142,7 @@ protected: double ratio, bool final); }; -D_Speex::D_Speex(Resampler::Quality quality, +D_Speex::D_Speex(Resampler::Quality quality, Resampler::RatioChange, int channels, double initialSampleRate, int maxBufferSize, int debugLevel) : m_resampler(0), @@ -1000,7 +1160,7 @@ D_Speex::D_Speex(Resampler::Quality quality, quality == Resampler::Fastest ? 0 : 4); if (m_debugLevel > 0) { - cerr << "Resampler::Resampler: using Speex implementation with q = " + cerr << "Resampler::Resampler: using implementation: Speex with q = " << q << endl; } @@ -1093,9 +1253,9 @@ D_Speex::setRatio(double ratio) } int -D_Speex::resample(float *const R__ *const R__ out, +D_Speex::resample(float *const BQ_R__ *const BQ_R__ out, int outcount, - const float *const R__ *const R__ in, + const float *const BQ_R__ *const BQ_R__ in, int incount, double ratio, bool final) @@ -1136,9 +1296,9 @@ D_Speex::resample(float *const R__ *const R__ out, } int -D_Speex::resampleInterleaved(float *const R__ out, +D_Speex::resampleInterleaved(float *const BQ_R__ out, int outcount, - const float *const R__ in, + const float *const BQ_R__ in, int incount, double ratio, bool final) @@ -1236,6 +1396,9 @@ Resampler::Resampler(Resampler::Parameters params, int channels) #ifdef HAVE_LIBRESAMPLE m_method = 3; #endif +#ifdef USE_BQRESAMPLER + m_method = 4; +#endif #ifdef HAVE_LIBSAMPLERATE m_method = 1; #endif @@ -1251,6 +1414,9 @@ Resampler::Resampler(Resampler::Parameters params, int channels) #ifdef USE_SPEEX m_method = 2; #endif +#ifdef USE_BQRESAMPLER + m_method = 4; +#endif #ifdef HAVE_LIBSAMPLERATE m_method = 1; #endif @@ -1266,6 +1432,9 @@ Resampler::Resampler(Resampler::Parameters params, int channels) #ifdef USE_SPEEX m_method = 2; #endif +#ifdef USE_BQRESAMPLER + m_method = 4; +#endif #ifdef HAVE_LIBSAMPLERATE m_method = 1; #endif @@ -1281,7 +1450,7 @@ Resampler::Resampler(Resampler::Parameters params, int channels) case 0: #ifdef HAVE_IPP d = new Resamplers::D_IPP - (params.quality, + (params.quality, params.ratioChange, channels, params.initialSampleRate, params.maxBufferSize, params.debugLevel); #else @@ -1293,7 +1462,7 @@ Resampler::Resampler(Resampler::Parameters params, int channels) case 1: #ifdef HAVE_LIBSAMPLERATE d = new Resamplers::D_SRC - (params.quality, + (params.quality, params.ratioChange, channels, params.initialSampleRate, params.maxBufferSize, params.debugLevel); #else @@ -1305,7 +1474,7 @@ Resampler::Resampler(Resampler::Parameters params, int channels) case 2: #ifdef USE_SPEEX d = new Resamplers::D_Speex - (params.quality, + (params.quality, params.ratioChange, channels, params.initialSampleRate, params.maxBufferSize, params.debugLevel); #else @@ -1317,12 +1486,21 @@ Resampler::Resampler(Resampler::Parameters params, int channels) case 3: #ifdef HAVE_LIBRESAMPLE d = new Resamplers::D_Resample - (params.quality, + (params.quality, params.ratioChange, channels, params.initialSampleRate, params.maxBufferSize, params.debugLevel); #else cerr << "Resampler::Resampler: No implementation available!" << endl; abort(); +#endif + break; + + case 4: +#ifdef USE_BQRESAMPLER + d = new Resamplers::D_BQResampler(params, channels); +#else + cerr << "Resampler::Resampler: No implementation available!" << endl; + abort(); #endif break; } @@ -1340,26 +1518,24 @@ Resampler::~Resampler() } int -Resampler::resample(float *const R__ *const R__ out, +Resampler::resample(float *const BQ_R__ *const BQ_R__ out, int outcount, - const float *const R__ *const R__ in, + const float *const BQ_R__ *const BQ_R__ in, int incount, double ratio, bool final) { - Profiler profiler("Resampler::resample"); return d->resample(out, outcount, in, incount, ratio, final); } int -Resampler::resampleInterleaved(float *const R__ out, +Resampler::resampleInterleaved(float *const BQ_R__ out, int outcount, - const float *const R__ in, + const float *const BQ_R__ in, int incount, double ratio, bool final) { - Profiler profiler("Resampler::resampleInterleaved"); return d->resampleInterleaved(out, outcount, in, incount, ratio, final); } diff --git a/src/dsp/Resampler.h b/src/dsp/Resampler.h index 4a1f723..1bb28a8 100644 --- a/src/dsp/Resampler.h +++ b/src/dsp/Resampler.h @@ -1,4 +1,4 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +//* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ /* Rubber Band Library @@ -32,6 +32,9 @@ class Resampler { public: enum Quality { Best, FastestTolerable, Fastest }; + enum Dynamism { RatioOftenChanging, RatioMostlyFixed }; + enum RatioChange { SmoothRatioChange, SuddenRatioChange }; + enum Exception { ImplementationError }; struct Parameters { @@ -41,6 +44,22 @@ public: */ Quality quality; + /** + * Performance hint indicating whether the ratio is expected + * to change regularly or not. If not, more work may happen on + * ratio changes to reduce work when ratio is unchanged. + */ + Dynamism dynamism; + + /** + * Hint indicating whether to smooth transitions, via filter + * interpolation or some such method, at ratio change + * boundaries, or whether to make a precise switch to the new + * ratio without regard to audible artifacts. The actual + * effect of this depends on the implementation in use. + */ + RatioChange ratioChange; + /** * Rate of expected input prior to resampling: may be used to * determine the filter bandwidth for the quality setting. If @@ -67,6 +86,8 @@ public: Parameters() : quality(FastestTolerable), + dynamism(RatioMostlyFixed), + ratioChange(SmoothRatioChange), initialSampleRate(44100), maxBufferSize(0), debugLevel(0) { } diff --git a/src/system/Allocators.h b/src/system/Allocators.h index 355bf29..1536954 100644 --- a/src/system/Allocators.h +++ b/src/system/Allocators.h @@ -29,6 +29,8 @@ #include // for std::bad_alloc #include +#include + #ifndef HAVE_POSIX_MEMALIGN #ifndef _WIN32 #ifndef __APPLE__ @@ -309,6 +311,96 @@ private: T *m_t; }; +/** Allocator for use with STL classes, e.g. vector, to ensure + * alignment. Based on example code by Stephan T. Lavavej. + * + * e.g. std::vector > v; + */ +template +class StlAllocator +{ +public: + typedef T *pointer; + typedef const T *const_pointer; + typedef T &reference; + typedef const T &const_reference; + typedef T value_type; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + + StlAllocator() { } + StlAllocator(const StlAllocator&) { } + template StlAllocator(const StlAllocator&) { } + ~StlAllocator() { } + + T * + allocate(const size_t n) const { + if (n == 0) return 0; + if (n > max_size()) { +#ifndef NO_EXCEPTIONS + throw std::length_error("Size overflow in StlAllocator::allocate()"); +#else + abort(); +#endif + } + return ::RubberBand::allocate(n); + } + + void + deallocate(T *const p, const size_t) const { + ::RubberBand::deallocate(p); + } + + template + T * + allocate(const size_t n, const U *) const { + return allocate(n); + } + + T * + address(T &r) const { + return &r; + } + + const T * + address(const T &s) const { + return &s; + } + + size_t + max_size() const { + return (static_cast(0) - static_cast(1)) / sizeof(T); + } + + template struct rebind { + typedef StlAllocator other; + }; + + bool + operator==(const StlAllocator &) const { + return true; + } + + bool + operator!=(const StlAllocator &) const { + return false; + } + + void + construct(T *const p, const T &t) const { + void *const pv = static_cast(p); + new (pv) T(t); + } + + void + destroy(T *const p) const { + p->~T(); + } + +private: + StlAllocator& operator=(const StlAllocator&); +}; + } #endif diff --git a/src/system/VectorOps.h b/src/system/VectorOps.h index ced40fc..5287be7 100644 --- a/src/system/VectorOps.h +++ b/src/system/VectorOps.h @@ -491,6 +491,58 @@ inline T v_sum(const T *const R__ src, return result; } +template +inline T v_multiply_and_sum(const T *const R__ src1, + const T *const R__ src2, + const int count) +{ + T result = T(); + for (int i = 0; i < count; ++i) { + result += src1[i] * src2[i]; + } + return result; +} + +#if defined HAVE_IPP +template<> +inline float v_multiply_and_sum(const float *const R__ src1, + const float *const R__ src2, + const int count) +{ + float dp; + ippsDotProd_32f(src1, src2, count, &dp); + return dp; +} +template<> +inline double v_multiply_and_sum(const double *const R__ src1, + const double *const R__ src2, + const int count) +{ + double dp; + ippsDotProd_64f(src1, src2, count, &dp); + return dp; +} +#elif defined HAVE_VDSP +template<> +inline float v_multiply_and_sum(const float *const R__ src1, + const float *const R__ src2, + const int count) +{ + float dp; + vDSP_dotpr(src1, 1, src2, 1, &dp, count); + return dp; +} +template<> +inline double v_multiply_and_sum(const double *const R__ src1, + const double *const R__ src2, + const int count) +{ + double dp; + vDSP_dotprD(src1, 1, src2, 1, &dp, count); + return dp; +} +#endif + template inline void v_log(T *const R__ dst, const int count) From b7770c8832a1809fe87ca658ff44ce25f4ad3faa Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 11 May 2021 17:20:12 +0100 Subject: [PATCH 04/32] Use the appropriate parameters --- src/StretcherImpl.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/StretcherImpl.cpp b/src/StretcherImpl.cpp index de7f084..19813a6 100644 --- a/src/StretcherImpl.cpp +++ b/src/StretcherImpl.cpp @@ -674,6 +674,8 @@ RubberBandStretcher::Impl::configure() Resampler::Parameters params; params.quality = Resampler::FastestTolerable; + params.dynamism = Resampler::RatioOftenChanging; + params.ratioChange = Resampler::SmoothRatioChange; params.maxBufferSize = 4096 * 16; params.debugLevel = m_debugLevel; @@ -818,6 +820,8 @@ RubberBandStretcher::Impl::reconfigure() Resampler::Parameters params; params.quality = Resampler::FastestTolerable; + params.dynamism = Resampler::RatioOftenChanging; + params.ratioChange = Resampler::SmoothRatioChange; params.maxBufferSize = m_sWindowSize; params.debugLevel = m_debugLevel; From 94d4467bfe34f38d646b8a5e05fcbd3ba50c45df Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 12 May 2021 11:12:02 +0100 Subject: [PATCH 05/32] Fix tsan reports. This does mean updating to C++11 --- src/StretcherChannelData.h | 9 ++++----- src/StretcherImpl.cpp | 4 ++-- src/base/RingBuffer.h | 23 +++++++++++------------ 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/StretcherChannelData.h b/src/StretcherChannelData.h index 2147571..aa718e3 100644 --- a/src/StretcherChannelData.h +++ b/src/StretcherChannelData.h @@ -27,8 +27,7 @@ #include "StretcherImpl.h" #include - -//#define EXPERIMENT 1 +#include namespace RubberBand { @@ -124,11 +123,11 @@ public: size_t chunkCount; size_t inCount; - long inputSize; // set only after known (when data ended); -1 previously + std::atomic inputSize; // set only after known (when data ended); -1 previously size_t outCount; - bool draining; - bool outputComplete; + std::atomic draining; + std::atomic outputComplete; FFT *fft; std::map ffts; diff --git a/src/StretcherImpl.cpp b/src/StretcherImpl.cpp index 19813a6..a955299 100644 --- a/src/StretcherImpl.cpp +++ b/src/StretcherImpl.cpp @@ -674,7 +674,7 @@ RubberBandStretcher::Impl::configure() Resampler::Parameters params; params.quality = Resampler::FastestTolerable; - params.dynamism = Resampler::RatioOftenChanging; + params.dynamism = Resampler::RatioMostlyFixed; params.ratioChange = Resampler::SmoothRatioChange; params.maxBufferSize = 4096 * 16; params.debugLevel = m_debugLevel; @@ -820,7 +820,7 @@ RubberBandStretcher::Impl::reconfigure() Resampler::Parameters params; params.quality = Resampler::FastestTolerable; - params.dynamism = Resampler::RatioOftenChanging; + params.dynamism = Resampler::RatioMostlyFixed; params.ratioChange = Resampler::SmoothRatioChange; params.maxBufferSize = m_sWindowSize; params.debugLevel = m_debugLevel; diff --git a/src/base/RingBuffer.h b/src/base/RingBuffer.h index 3586b91..cd9df19 100644 --- a/src/base/RingBuffer.h +++ b/src/base/RingBuffer.h @@ -33,6 +33,8 @@ #include +#include + namespace RubberBand { /** @@ -174,11 +176,11 @@ public: int zero(int n); protected: - T *const R__ m_buffer; - int m_writer; - int m_reader; - const int m_size; - bool m_mlocked; + T *const R__ m_buffer; + std::atomic m_writer; + std::atomic m_reader; + const int m_size; + bool m_mlocked; int readSpaceFor(int w, int r) const { int space; @@ -243,7 +245,8 @@ RingBuffer * RingBuffer::resized(int newSize) const { RingBuffer *newBuffer = new RingBuffer(newSize); - + + MBARRIER(); int w = m_writer; int r = m_reader; @@ -273,7 +276,8 @@ RingBuffer::reset() std::cerr << "RingBuffer[" << this << "]::reset" << std::endl; #endif - m_reader = m_writer; + int r = m_reader; + m_writer = r; } template @@ -302,7 +306,6 @@ RingBuffer::read(S *const R__ destination, int n) if (n > available) { std::cerr << "WARNING: RingBuffer::read: " << n << " requested, only " << available << " available" << std::endl; -//!!! v_zero(destination + available, n - available); n = available; } if (n == 0) return n; @@ -320,7 +323,6 @@ RingBuffer::read(S *const R__ destination, int n) r += n; while (r >= m_size) r -= m_size; - MBARRIER(); m_reader = r; return n; @@ -355,7 +357,6 @@ RingBuffer::readAdding(S *const R__ destination, int n) r += n; while (r >= m_size) r -= m_size; - MBARRIER(); m_reader = r; return n; @@ -377,7 +378,6 @@ RingBuffer::readOne() T value = m_buffer[r]; if (++r == m_size) r = 0; - MBARRIER(); m_reader = r; return value; @@ -447,7 +447,6 @@ RingBuffer::skip(int n) r += n; while (r >= m_size) r -= m_size; - // No memory barrier required, because we didn't read any data m_reader = r; return n; From c1fd6fe6a5ac76063f0b0725e2996382bfd3018a Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 12 May 2021 17:26:27 +0100 Subject: [PATCH 06/32] Fix overrun --- src/dsp/BQResampler.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/dsp/BQResampler.cpp b/src/dsp/BQResampler.cpp index 2a628f8..2ec624e 100644 --- a/src/dsp/BQResampler.cpp +++ b/src/dsp/BQResampler.cpp @@ -581,15 +581,17 @@ BQResampler::reconstruct_one(state *s) const int phase_length = pr.length; double result = 0.0; + int dot_length = min(phase_length, int(s->buffer.size()) - s->left); + if (m_dynamism == RatioMostlyFixed) { int phase_start = pr.start_index; if (m_channels == 1) { result = v_multiply_and_sum (s->phase_sorted_filter.data() + phase_start, s->buffer.data() + s->left, - phase_length); + dot_length); } else { - for (int i = 0; i < phase_length; ++i) { + for (int i = 0; i < dot_length; ++i) { result += s->phase_sorted_filter[phase_start + i] * s->buffer[s->left + i * m_channels + s->current_channel]; @@ -597,7 +599,7 @@ BQResampler::reconstruct_one(state *s) const } } else { double m = double(m_proto_length - 1) / double(s->filter_length - 1); - for (int i = 0; i < phase_length; ++i) { + for (int i = 0; i < dot_length; ++i) { double sample = s->buffer[s->left + i * m_channels + s->current_channel]; int filter_index = i * s->parameters.numerator + s->current_phase; From 6aeb71392138ab8e8098aedd96d6e395094fbda9 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Wed, 12 May 2021 17:29:26 +0100 Subject: [PATCH 07/32] Use proper parameters for processing mode --- src/StretcherImpl.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/StretcherImpl.cpp b/src/StretcherImpl.cpp index a955299..ca5b52a 100644 --- a/src/StretcherImpl.cpp +++ b/src/StretcherImpl.cpp @@ -674,8 +674,16 @@ RubberBandStretcher::Impl::configure() Resampler::Parameters params; params.quality = Resampler::FastestTolerable; - params.dynamism = Resampler::RatioMostlyFixed; - params.ratioChange = Resampler::SmoothRatioChange; + + if (m_realtime) { + params.dynamism = Resampler::RatioOftenChanging; + params.ratioChange = Resampler::SmoothRatioChange; + } else { + // ratio can't be changed in offline mode + params.dynamism = Resampler::RatioMostlyFixed; + params.ratioChange = Resampler::SuddenRatioChange; + } + params.maxBufferSize = 4096 * 16; params.debugLevel = m_debugLevel; @@ -820,7 +828,7 @@ RubberBandStretcher::Impl::reconfigure() Resampler::Parameters params; params.quality = Resampler::FastestTolerable; - params.dynamism = Resampler::RatioMostlyFixed; + params.dynamism = Resampler::RatioOftenChanging; params.ratioChange = Resampler::SmoothRatioChange; params.maxBufferSize = m_sWindowSize; params.debugLevel = m_debugLevel; From c79c426e800923307c5fcbcfe6a5516fee3c0d0f Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 13 May 2021 08:51:27 +0100 Subject: [PATCH 08/32] In this branch, at least, we are now C++11 --- otherbuilds/Makefile.linux | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/otherbuilds/Makefile.linux b/otherbuilds/Makefile.linux index b6d4118..ec361bf 100644 --- a/otherbuilds/Makefile.linux +++ b/otherbuilds/Makefile.linux @@ -6,7 +6,7 @@ OPTFLAGS := -DNDEBUG -ffast-math -O3 -ftree-vectorize ARCHFLAGS := -CXXFLAGS := -std=c++98 $(ARCHFLAGS) $(OPTFLAGS) -I. -Isrc -Irubberband -DHAVE_LIBSAMPLERATE -DUSE_BUILTIN_FFT -DNO_THREAD_CHECKS -DUSE_PTHREADS -DNO_TIMING -DHAVE_POSIX_MEMALIGN -DNDEBUG +CXXFLAGS := -std=c++11 $(ARCHFLAGS) $(OPTFLAGS) -I. -Isrc -Irubberband -DHAVE_LIBSAMPLERATE -DUSE_BUILTIN_FFT -DNO_THREAD_CHECKS -DUSE_PTHREADS -DNO_TIMING -DHAVE_POSIX_MEMALIGN -DNDEBUG CFLAGS := $(ARCHFLAGS) $(OPTFLAGS) From d06b4efc1640471005265c1071b0957bdf6a557c Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 13 May 2021 18:04:43 +0100 Subject: [PATCH 09/32] Toward more accurate timing in the face of varying pitch ratio --- main/main.cpp | 7 +- src/StretchCalculator.cpp | 205 ++++++++++++++++++++++++++++++-------- src/StretchCalculator.h | 17 +++- src/StretcherImpl.cpp | 21 +++- src/StretcherProcess.cpp | 16 ++- src/dsp/BQResampler.cpp | 9 ++ src/dsp/BQResampler.h | 2 + src/dsp/Resampler.cpp | 19 +++- src/dsp/Resampler.h | 2 + 9 files changed, 242 insertions(+), 56 deletions(-) diff --git a/main/main.cpp b/main/main.cpp index b13e721..6198e22 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -500,11 +500,12 @@ int main(int argc, char **argv) if (shortwin) options |= RubberBandStretcher::OptionWindowShort; if (smoothing) options |= RubberBandStretcher::OptionSmoothingOn; if (formant) options |= RubberBandStretcher::OptionFormantPreserved; - if (hqpitch) options |= RubberBandStretcher::OptionPitchHighQuality; if (together) options |= RubberBandStretcher::OptionChannelsTogether; if (freqOrPitchMapSpecified) { options |= RubberBandStretcher::OptionPitchHighConsistency; + } else if (hqpitch) { + options |= RubberBandStretcher::OptionPitchHighQuality; } switch (threading) { @@ -647,13 +648,13 @@ int main(int argc, char **argv) int thisBlockSize = ibs; while (freqMapItr != freqMap.end()) { - size_t nextFreqFrame = freqMapItr->first + ts.getLatency(); + size_t nextFreqFrame = freqMapItr->first; // + ts.getLatency(); if (nextFreqFrame <= countIn) { double s = frequencyshift * freqMapItr->second; if (debug > 0) { cerr << "at frame " << countIn << " (requested at " << freqMapItr->first - << " plus latency " << ts.getLatency() + << " [NOT] plus latency " << ts.getLatency() << ") updating frequency ratio to " << s << endl; } ts.setPitchScale(s); diff --git a/src/StretchCalculator.cpp b/src/StretchCalculator.cpp index 2886ec3..a6d1397 100644 --- a/src/StretchCalculator.cpp +++ b/src/StretchCalculator.cpp @@ -44,9 +44,13 @@ StretchCalculator::StretchCalculator(size_t sampleRate, m_divergence(0), m_recovery(0), m_prevRatio(1.0), + m_prevTimeRatio(1.0), m_transientAmnesty(0), m_debugLevel(0), - m_useHardPeaks(useHardPeaks) + m_useHardPeaks(useHardPeaks), + m_inFrameCounter(0), + m_frameCheckpoint(0, 0), + m_outFrameCounter(0) { // std::cerr << "StretchCalculator::StretchCalculator: useHardPeaks = " << useHardPeaks << std::endl; } @@ -318,18 +322,107 @@ StretchCalculator::mapPeaks(std::vector &peaks, } } -int -StretchCalculator::calculateSingle(double ratio, - float df, - size_t increment) +int64_t +StretchCalculator::expectedOutFrame(int64_t inFrame, double timeRatio) { + int64_t checkpointedAt = m_frameCheckpoint.first; + int64_t checkpointed = m_frameCheckpoint.second; + return int64_t(round(checkpointed + (inFrame - checkpointedAt) * timeRatio)); +} + +int +StretchCalculator::calculateSingle(double timeRatio, + double effectivePitchRatio, + float df, + size_t inIncrement, + size_t analysisWindowSize, + size_t synthesisWindowSize) +{ + double ratio = timeRatio / effectivePitchRatio; + + int increment = int(inIncrement); if (increment == 0) increment = m_increment; + int outIncrement = lrint(increment * ratio); // the normal case bool isTransient = false; - + // We want to ensure, as close as possible, that the phase reset - // points appear at _exactly_ the right audio frame numbers. + // points appear at the right audio frame numbers. To this end we + // track the incoming frame number, its corresponding expected + // output frame number, and the actual output frame number + // projected based on the ratios provided. + // + // There are two subtleties: + // + // (1) on a ratio change, we need to checkpoint the expected + // output frame number reached so far and start counting again + // with the new ratio. We could do this with a reset to zero, but + // it's easier to reason about absolute input/output frame + // matches, so for the moment at least we're doing this by + // explicitly checkpointing the current numbers (hence the use of + // the above expectedOutFrame() function which refers to the + // last checkpointed values). + // + // (2) in the case of a pitch shift in a configuration where + // resampling occurs after stretching, all of our output + // increments will be effectively modified by resampling after we + // return. This is why we separate out timeRatio and + // effectivePitchRatio arguments - the former is the ratio that + // has already been applied and the latter is the ratio that will + // be applied by any subsequent resampling step (which will be 1.0 + // / pitchScale if resampling is happening after stretching). So + // the overall ratio is timeRatio / effectivePitchRatio. + bool ratioChanged = (ratio != m_prevRatio); + if (ratioChanged) { + // Reset our frame counters from the ratio change. + + // m_outFrameCounter tracks the frames counted at output from + // this function, which normally precedes resampling - hence + // the use of timeRatio rather than ratio here + + if (m_debugLevel > 1) { + std::cerr << "StretchCalculator: ratio changed from " << m_prevRatio << " to " << ratio << std::endl; + } + + int64_t toCheckpoint = expectedOutFrame + (m_inFrameCounter, m_prevTimeRatio); + m_frameCheckpoint = + std::pair(m_inFrameCounter, toCheckpoint); + } + + m_prevRatio = ratio; + m_prevTimeRatio = timeRatio; + + if (m_debugLevel > 2) { + std::cerr << "StretchCalculator::calculateSingle: timeRatio = " + << timeRatio << ", effectivePitchRatio = " + << effectivePitchRatio << " (that's 1.0 / " + << (1.0 / effectivePitchRatio) + << "), ratio = " << ratio << ", df = " << df + << ", inIncrement = " << inIncrement + << ", default outIncrement = " << outIncrement + << ", analysisWindowSize = " << analysisWindowSize + << ", synthesisWindowSize = " << synthesisWindowSize + << std::endl; + + std::cerr << "inFrameCounter = " << m_inFrameCounter + << ", outFrameCounter = " << m_outFrameCounter + << std::endl; + + std::cerr << "The next sample out is input sample " << m_inFrameCounter << std::endl; + } + + int64_t intended = expectedOutFrame + (m_inFrameCounter + analysisWindowSize/4, timeRatio); + int64_t projected = int64_t + (round(m_outFrameCounter + (synthesisWindowSize/4 * effectivePitchRatio))); + m_divergence = projected - intended; + + if (m_debugLevel > 2) { + std::cerr << "for current frame + quarter frame: intended " << intended << ", projected " << projected << ", divergence " << m_divergence << std::endl; + } + // In principle, the threshold depends on chunk size: larger chunk // sizes need higher thresholds. Since chunk size depends on // ratio, I suppose we could in theory calculate the threshold @@ -350,53 +443,81 @@ StretchCalculator::calculateSingle(double ratio, m_prevDf = df; - bool ratioChanged = (ratio != m_prevRatio); - m_prevRatio = ratio; - - if (isTransient && m_transientAmnesty == 0) { - if (m_debugLevel > 1) { - std::cerr << "StretchCalculator::calculateSingle: transient (df " << df << ", threshold " << transientThreshold << ")" << std::endl; + if (m_transientAmnesty > 0) { + if (isTransient) { + if (m_debugLevel > 1) { + std::cerr << "StretchCalculator::calculateSingle: transient, but we have an amnesty (df " << df << ", threshold " << transientThreshold << ")" << std::endl; + } + isTransient = false; + } + --m_transientAmnesty; + } + + if (isTransient) { + if (m_debugLevel > 1) { + std::cerr << "StretchCalculator::calculateSingle: transient at (df " << df << ", threshold " << transientThreshold << ")" << std::endl; } - m_divergence += increment - (increment * ratio); // as in offline mode, 0.05 sec approx min between transients m_transientAmnesty = lrint(ceil(double(m_sampleRate) / (20 * double(increment)))); m_recovery = m_divergence / ((m_sampleRate / 10.0) / increment); - return -int(increment); + + outIncrement = increment; + + } else { + + if (ratioChanged) { + m_recovery = m_divergence / ((m_sampleRate / 10.0) / increment); + } + + int incr = lrint(outIncrement - m_recovery); + if (m_debugLevel > 2 || (m_debugLevel > 1 && m_divergence != 0)) { + std::cerr << "divergence = " << m_divergence << ", recovery = " << m_recovery << ", incr = " << incr << ", "; + } + if (incr < lrint((increment * ratio) / 2)) { + incr = lrint((increment * ratio) / 2); + } else if (incr > lrint(increment * ratio * 2)) { + incr = lrint(increment * ratio * 2); + } + + double divdiff = (increment * ratio) - incr; + + if (m_debugLevel > 2 || (m_debugLevel > 1 && m_divergence != 0)) { + std::cerr << "possibly clamped to " << incr << ", divdiff = " << divdiff << std::endl; + } + + double prevDivergence = m_divergence; + m_divergence -= divdiff; + if ((prevDivergence < 0 && m_divergence > 0) || + (prevDivergence > 0 && m_divergence < 0)) { + m_recovery = m_divergence / ((m_sampleRate / 10.0) / increment); + } + + if (incr < 0) { + std::cerr << "WARNING: internal error: incr < 0 in calculateSingle" + << std::endl; + outIncrement = 0; + } else { + outIncrement = incr; + } } - if (ratioChanged) { - m_recovery = m_divergence / ((m_sampleRate / 10.0) / increment); + if (m_debugLevel > 1) { + std::cerr << "StretchCalculator::calculateSingle: returning isTransient = " + << isTransient << ", outIncrement = " << outIncrement + << std::endl; } - if (m_transientAmnesty > 0) --m_transientAmnesty; - - int incr = lrint(increment * ratio - m_recovery); - if (m_debugLevel > 2 || (m_debugLevel > 1 && m_divergence != 0)) { - std::cerr << "divergence = " << m_divergence << ", recovery = " << m_recovery << ", incr = " << incr << ", "; + m_inFrameCounter += inIncrement; + m_outFrameCounter += outIncrement * effectivePitchRatio; + + if (isTransient) { + return -outIncrement; + } else { + return outIncrement; } - if (incr < lrint((increment * ratio) / 2)) { - incr = lrint((increment * ratio) / 2); - } else if (incr > lrint(increment * ratio * 2)) { - incr = lrint(increment * ratio * 2); - } - - double divdiff = (increment * ratio) - incr; - - if (m_debugLevel > 2 || (m_debugLevel > 1 && m_divergence != 0)) { - std::cerr << "divdiff = " << divdiff << std::endl; - } - - double prevDivergence = m_divergence; - m_divergence -= divdiff; - if ((prevDivergence < 0 && m_divergence > 0) || - (prevDivergence > 0 && m_divergence < 0)) { - m_recovery = m_divergence / ((m_sampleRate / 10.0) / increment); - } - - return incr; } void diff --git a/src/StretchCalculator.h b/src/StretchCalculator.h index fc3822d..4f05e3e 100644 --- a/src/StretchCalculator.h +++ b/src/StretchCalculator.h @@ -68,8 +68,12 @@ public: * If increment is non-zero, use it for the input increment for * this block in preference to m_increment. */ - int calculateSingle(double ratio, float curveValue, - size_t increment = 0); + int calculateSingle(double timeRatio, + double effectivePitchRatio, + float curveValue, + size_t increment, + size_t analysisWindowSize, + size_t synthesisWindowSize); void setUseHardPeaks(bool use) { m_useHardPeaks = use; } @@ -105,11 +109,16 @@ protected: size_t m_increment; float m_prevDf; double m_divergence; - float m_recovery; - float m_prevRatio; + double m_recovery; + double m_prevRatio; + double m_prevTimeRatio; int m_transientAmnesty; // only in RT mode; handled differently offline int m_debugLevel; bool m_useHardPeaks; + int64_t m_inFrameCounter; + std::pair m_frameCheckpoint; + int64_t expectedOutFrame(int64_t inFrame, double timeRatio); + double m_outFrameCounter; std::map m_keyFrameMap; std::vector m_peaks; diff --git a/src/StretcherImpl.cpp b/src/StretcherImpl.cpp index ca5b52a..cc7beb6 100644 --- a/src/StretcherImpl.cpp +++ b/src/StretcherImpl.cpp @@ -738,11 +738,12 @@ RubberBandStretcher::Impl::configure() // number of onset detector chunks will be the number of audio // samples input, divided by the input increment, plus one. + //!!! // In real-time mode, we don't do this prefill -- it's better to // start with a swoosh than introduce more latency, and we don't // want gaps when the ratio changes. - if (!m_realtime) { +// if (!m_realtime) { if (m_debugLevel > 1) { cerr << "Not real time mode: prefilling" << endl; } @@ -750,7 +751,7 @@ RubberBandStretcher::Impl::configure() m_channelData[c]->reset(); m_channelData[c]->inbuf->zero(m_aWindowSize/2); } - } +// } } @@ -777,6 +778,8 @@ RubberBandStretcher::Impl::reconfigure() calculateSizes(); + bool somethingChanged = false; + // There are various allocations in this function, but they should // never happen in normal use -- they just recover from the case // where not all of the things we need were correctly created when @@ -811,12 +814,15 @@ RubberBandStretcher::Impl::reconfigure() m_channelData[c]->setSizes(std::max(m_aWindowSize, m_sWindowSize), m_fftSize); } + + somethingChanged = true; } if (m_outbufSize != prevOutbufSize) { for (size_t c = 0; c < m_channels; ++c) { m_channelData[c]->setOutbufSize(m_outbufSize); } + somethingChanged = true; } if (m_pitchScale != 1.0) { @@ -839,11 +845,22 @@ RubberBandStretcher::Impl::reconfigure() lrintf(ceil((m_increment * m_timeRatio * 2) / m_pitchScale)); if (rbs < m_increment * 16) rbs = m_increment * 16; m_channelData[c]->setResampleBufSize(rbs); + + somethingChanged = true; } } if (m_fftSize != prevFftSize) { m_phaseResetAudioCurve->setFftSize(m_fftSize); + somethingChanged = true; + } + + if (m_debugLevel > 0) { + if (somethingChanged) { + std::cerr << "reconfigure: at least one parameter changed" << std::endl; + } else { + std::cerr << "reconfigure: nothing changed" << std::endl; + } } } diff --git a/src/StretcherProcess.cpp b/src/StretcherProcess.cpp index b8f5a49..fdc697e 100644 --- a/src/StretcherProcess.cpp +++ b/src/StretcherProcess.cpp @@ -616,8 +616,14 @@ RubberBandStretcher::Impl::calculateIncrements(size_t &phaseIncrementRtn, } } + double effectivePitchRatio = 1.0 / m_pitchScale; + if (cd.resampler) { + effectivePitchRatio = cd.resampler->getEffectiveRatio(effectivePitchRatio); + } + int incr = m_stretchCalculator->calculateSingle - (getEffectiveRatio(), df, m_increment); + (m_timeRatio, effectivePitchRatio, df, m_increment, + m_aWindowSize, m_sWindowSize); if (m_lastProcessPhaseResetDf.getWriteSpace() > 0) { m_lastProcessPhaseResetDf.write(&df, 1); @@ -1142,11 +1148,13 @@ RubberBandStretcher::Impl::writeOutput(RingBuffer &to, float *from, size_ // samples, because the first chunk is centred on the start of the // output. In RT mode we didn't apply any pre-padding in // configure(), so we don't want to remove any here. - +//!!! size_t startSkip = 0; - if (!m_realtime) { +// if (!m_realtime) { + //!!! lock down the latency to this initial value in RT mode startSkip = lrintf((m_sWindowSize/2) / m_pitchScale); - } +// startSkip = m_sWindowSize/2; +// } if (outCount > startSkip) { diff --git a/src/dsp/BQResampler.cpp b/src/dsp/BQResampler.cpp index 2ec624e..0804a28 100644 --- a/src/dsp/BQResampler.cpp +++ b/src/dsp/BQResampler.cpp @@ -220,6 +220,15 @@ BQResampler::resampleInterleaved(float *const out, return o / m_channels; } +double +BQResampler::getEffectiveRatio(double ratio) const { + if (m_initialised && ratio == m_s->parameters.ratio) { + return m_s->parameters.effective; + } else { + return pick_params(ratio).effective; + } +} + int BQResampler::gcd(int a, int b) const { diff --git a/src/dsp/BQResampler.h b/src/dsp/BQResampler.h index c05af1d..9ca0603 100644 --- a/src/dsp/BQResampler.h +++ b/src/dsp/BQResampler.h @@ -60,6 +60,8 @@ public: const float *const in, int incount, double ratio, bool final); + double getEffectiveRatio(double ratio) const; + void reset(); private: diff --git a/src/dsp/Resampler.cpp b/src/dsp/Resampler.cpp index dda917d..13336f5 100644 --- a/src/dsp/Resampler.cpp +++ b/src/dsp/Resampler.cpp @@ -99,6 +99,7 @@ public: bool final) = 0; virtual int getChannelCount() const = 0; + virtual double getEffectiveRatio(double ratio) const = 0; virtual void reset() = 0; }; @@ -130,6 +131,7 @@ public: bool final = false); int getChannelCount() const { return m_channels; } + double getEffectiveRatio(double ratio) const { return ratio; } void reset(); @@ -561,6 +563,7 @@ public: bool final = false); int getChannelCount() const { return m_channels; } + double getEffectiveRatio(double ratio) const { return ratio; } void reset(); @@ -785,6 +788,7 @@ public: bool final); int getChannelCount() const { return m_channels; } + double getEffectiveRatio(double ratio) const { return ratio; } void reset(); @@ -970,7 +974,13 @@ public: double ratio, bool final = false); - int getChannelCount() const { return m_channels; } + int getChannelCount() const { + return m_channels; + } + + double getEffectiveRatio(double ratio) const { + return m_resampler->getEffectiveRatio(ratio); + } void reset(); @@ -1121,6 +1131,7 @@ public: bool final = false); int getChannelCount() const { return m_channels; } + double getEffectiveRatio(double ratio) const { return ratio; } void reset(); @@ -1545,6 +1556,12 @@ Resampler::getChannelCount() const return d->getChannelCount(); } +double +Resampler::getEffectiveRatio(double ratio) const +{ + return d->getEffectiveRatio(ratio); +} + void Resampler::reset() { diff --git a/src/dsp/Resampler.h b/src/dsp/Resampler.h index 1bb28a8..775fda7 100644 --- a/src/dsp/Resampler.h +++ b/src/dsp/Resampler.h @@ -148,6 +148,8 @@ public: int getChannelCount() const; + double getEffectiveRatio(double ratio) const; + void reset(); class Impl; From 478d9233fe4b603e0e533ecca55cf9af1b35f1a2 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 14 May 2021 08:46:37 +0100 Subject: [PATCH 10/32] Fix calculation of recovery value now that divergence is recalculated afresh on each update --- .hgignore | 1 + src/StretchCalculator.cpp | 54 +++++++++++++++++++-------------------- src/StretchCalculator.h | 2 -- src/StretcherImpl.cpp | 4 +-- src/StretcherProcess.cpp | 4 +++ 5 files changed, 34 insertions(+), 31 deletions(-) diff --git a/.hgignore b/.hgignore index a10ed24..a335526 100644 --- a/.hgignore +++ b/.hgignore @@ -24,4 +24,5 @@ Release/ Debug/ build build_* +build-* UpgradeLog* diff --git a/src/StretchCalculator.cpp b/src/StretchCalculator.cpp index a6d1397..230cbd8 100644 --- a/src/StretchCalculator.cpp +++ b/src/StretchCalculator.cpp @@ -41,8 +41,6 @@ StretchCalculator::StretchCalculator(size_t sampleRate, m_sampleRate(sampleRate), m_increment(inputIncrement), m_prevDf(0), - m_divergence(0), - m_recovery(0), m_prevRatio(1.0), m_prevTimeRatio(1.0), m_transientAmnesty(0), @@ -417,10 +415,11 @@ StretchCalculator::calculateSingle(double timeRatio, (m_inFrameCounter + analysisWindowSize/4, timeRatio); int64_t projected = int64_t (round(m_outFrameCounter + (synthesisWindowSize/4 * effectivePitchRatio))); - m_divergence = projected - intended; + + int64_t divergence = projected - intended; if (m_debugLevel > 2) { - std::cerr << "for current frame + quarter frame: intended " << intended << ", projected " << projected << ", divergence " << m_divergence << std::endl; + std::cerr << "for current frame + quarter frame: intended " << intended << ", projected " << projected << ", divergence " << divergence << std::endl; } // In principle, the threshold depends on chunk size: larger chunk @@ -462,37 +461,36 @@ StretchCalculator::calculateSingle(double timeRatio, m_transientAmnesty = lrint(ceil(double(m_sampleRate) / (20 * double(increment)))); - m_recovery = m_divergence / ((m_sampleRate / 10.0) / increment); - outIncrement = increment; } else { - if (ratioChanged) { - m_recovery = m_divergence / ((m_sampleRate / 10.0) / increment); + double recovery = 0.0; + if (divergence > 1000 || divergence < -1000) { + recovery = divergence / ((m_sampleRate / 10.0) / increment); + } else if (divergence > 100 || divergence < -100) { + recovery = divergence / ((m_sampleRate / 25.0) / increment); + } else { + recovery = divergence / 4.0; } - int incr = lrint(outIncrement - m_recovery); - if (m_debugLevel > 2 || (m_debugLevel > 1 && m_divergence != 0)) { - std::cerr << "divergence = " << m_divergence << ", recovery = " << m_recovery << ", incr = " << incr << ", "; - } - if (incr < lrint((increment * ratio) / 2)) { - incr = lrint((increment * ratio) / 2); - } else if (incr > lrint(increment * ratio * 2)) { - incr = lrint(increment * ratio * 2); + int incr = lrint(outIncrement - recovery); + if (m_debugLevel > 2 || (m_debugLevel > 1 && divergence != 0)) { + std::cerr << "divergence = " << divergence << ", recovery = " << recovery << ", incr = " << incr << ", "; } - double divdiff = (increment * ratio) - incr; - - if (m_debugLevel > 2 || (m_debugLevel > 1 && m_divergence != 0)) { - std::cerr << "possibly clamped to " << incr << ", divdiff = " << divdiff << std::endl; + int minIncr = lrint(increment * ratio * 0.5); + int maxIncr = lrint(increment * ratio * 2); + + if (incr < minIncr) { + incr = minIncr; + } else if (incr > maxIncr) { + incr = maxIncr; } - double prevDivergence = m_divergence; - m_divergence -= divdiff; - if ((prevDivergence < 0 && m_divergence > 0) || - (prevDivergence > 0 && m_divergence < 0)) { - m_recovery = m_divergence / ((m_sampleRate / 10.0) / increment); + if (m_debugLevel > 2 || (m_debugLevel > 1 && divergence != 0)) { + std::cerr << "clamped into [" << minIncr << ", " << maxIncr + << "] becomes " << incr << std::endl; } if (incr < 0) { @@ -524,9 +522,11 @@ void StretchCalculator::reset() { m_prevDf = 0; - m_divergence = 0; - m_recovery = 0; m_prevRatio = 1.0; + m_prevTimeRatio = 1.0; + m_inFrameCounter = 0; + m_frameCheckpoint = std::pair(0, 0); + m_outFrameCounter = 0.0; m_transientAmnesty = 0; m_keyFrameMap.clear(); } diff --git a/src/StretchCalculator.h b/src/StretchCalculator.h index 4f05e3e..56c7de7 100644 --- a/src/StretchCalculator.h +++ b/src/StretchCalculator.h @@ -108,8 +108,6 @@ protected: size_t m_sampleRate; size_t m_increment; float m_prevDf; - double m_divergence; - double m_recovery; double m_prevRatio; double m_prevTimeRatio; int m_transientAmnesty; // only in RT mode; handled differently offline diff --git a/src/StretcherImpl.cpp b/src/StretcherImpl.cpp index cc7beb6..d545c8c 100644 --- a/src/StretcherImpl.cpp +++ b/src/StretcherImpl.cpp @@ -685,7 +685,7 @@ RubberBandStretcher::Impl::configure() } params.maxBufferSize = 4096 * 16; - params.debugLevel = m_debugLevel; + params.debugLevel = (m_debugLevel > 0 ? m_debugLevel-1 : 0); m_channelData[c]->resampler = new Resampler(params, 1); @@ -837,7 +837,7 @@ RubberBandStretcher::Impl::reconfigure() params.dynamism = Resampler::RatioOftenChanging; params.ratioChange = Resampler::SmoothRatioChange; params.maxBufferSize = m_sWindowSize; - params.debugLevel = m_debugLevel; + params.debugLevel = (m_debugLevel > 0 ? m_debugLevel-1 : 0); m_channelData[c]->resampler = new Resampler(params, 1); diff --git a/src/StretcherProcess.cpp b/src/StretcherProcess.cpp index fdc697e..5bdf80c 100644 --- a/src/StretcherProcess.cpp +++ b/src/StretcherProcess.cpp @@ -189,6 +189,8 @@ RubberBandStretcher::Impl::consumeChannel(size_t c, if (resampling) { + Profiler profiler2("RubberBandStretcher::Impl::resample"); + toWrite = int(ceil(samples / m_pitchScale)); if (writable < toWrite) { samples = int(floor(writable * m_pitchScale)); @@ -1077,6 +1079,8 @@ RubberBandStretcher::Impl::writeChunk(size_t channel, size_t shiftIncrement, boo (m_pitchScale != 1.0 || m_options & OptionPitchHighConsistency) && cd.resampler) { + Profiler profiler2("RubberBandStretcher::Impl::resample"); + size_t reqSize = int(ceil(si / m_pitchScale)); if (reqSize > cd.resamplebufSize) { // This shouldn't normally happen -- the buffer is From d9dfc0a84c833078686844b104b324560795811d Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 14 May 2021 11:35:08 +0100 Subject: [PATCH 11/32] Start updating LADSPA plugin for proper timing --- ladspa/RubberBandPitchShifter.cpp | 125 +++++++++++++++--------------- ladspa/RubberBandPitchShifter.h | 9 ++- src/StretchCalculator.cpp | 2 +- src/StretcherImpl.cpp | 3 +- src/StretcherProcess.cpp | 4 +- 5 files changed, 74 insertions(+), 69 deletions(-) diff --git a/ladspa/RubberBandPitchShifter.cpp b/ladspa/RubberBandPitchShifter.cpp index 868d59f..3ca1245 100644 --- a/ladspa/RubberBandPitchShifter.cpp +++ b/ladspa/RubberBandPitchShifter.cpp @@ -44,7 +44,7 @@ RubberBandPitchShifter::portNamesMono[PortCountMono] = "Octaves", "Crispness", "Formant Preserving", - "Faster", + "Wet-Dry Mix", "Input", "Output" }; @@ -58,7 +58,7 @@ RubberBandPitchShifter::portNamesStereo[PortCountStereo] = "Octaves", "Crispness", "Formant Preserving", - "Faster", + "Wet-Dry Mix", "Input L", "Output L", "Input R", @@ -123,10 +123,9 @@ RubberBandPitchShifter::hintsMono[PortCountMono] = LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_TOGGLED, 0.0, 1.0 }, - { LADSPA_HINT_DEFAULT_0 | // fast + { LADSPA_HINT_DEFAULT_0 | // wet-dry mix LADSPA_HINT_BOUNDED_BELOW | - LADSPA_HINT_BOUNDED_ABOVE | - LADSPA_HINT_TOGGLED, + LADSPA_HINT_BOUNDED_ABOVE, 0.0, 1.0 }, { 0, 0, 0 }, { 0, 0, 0 } @@ -160,10 +159,9 @@ RubberBandPitchShifter::hintsStereo[PortCountStereo] = LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_TOGGLED, 0.0, 1.0 }, - { LADSPA_HINT_DEFAULT_0 | // fast + { LADSPA_HINT_DEFAULT_0 | // wet-dry mix LADSPA_HINT_BOUNDED_BELOW | - LADSPA_HINT_BOUNDED_ABOVE | - LADSPA_HINT_TOGGLED, + LADSPA_HINT_BOUNDED_ABOVE, 0.0, 1.0 }, { 0, 0, 0 }, { 0, 0, 0 }, @@ -237,12 +235,11 @@ RubberBandPitchShifter::RubberBandPitchShifter(int sampleRate, size_t channels) m_octaves(0), m_crispness(0), m_formant(0), - m_fast(0), + m_wetDry(0), m_ratio(1.0), m_prevRatio(1.0), m_currentCrispness(-1), m_currentFormant(false), - m_currentFast(false), m_blockSize(1024), m_reserve(1024), m_minfill(0), @@ -257,6 +254,7 @@ RubberBandPitchShifter::RubberBandPitchShifter(int sampleRate, size_t channels) m_output = new float *[m_channels]; m_outputBuffer = new RingBuffer *[m_channels]; + m_delayMixBuffer = new RingBuffer *[m_channels]; m_scratch = new float *[m_channels]; for (size_t c = 0; c < m_channels; ++c) { @@ -267,6 +265,7 @@ RubberBandPitchShifter::RubberBandPitchShifter(int sampleRate, size_t channels) int bufsize = m_blockSize + m_reserve + 8192; m_outputBuffer[c] = new RingBuffer(bufsize); + m_delayMixBuffer[c] = new RingBuffer(bufsize); m_scratch[c] = new float[bufsize]; for (int i = 0; i < bufsize; ++i) m_scratch[c][i] = 0.f; @@ -280,9 +279,11 @@ RubberBandPitchShifter::~RubberBandPitchShifter() delete m_stretcher; for (size_t c = 0; c < m_channels; ++c) { delete m_outputBuffer[c]; + delete m_delayMixBuffer[c]; delete[] m_scratch[c]; } delete[] m_outputBuffer; + delete[] m_delayMixBuffer; delete[] m_scratch; delete[] m_output; delete[] m_input; @@ -312,7 +313,7 @@ RubberBandPitchShifter::connectPort(LADSPA_Handle handle, &shifter->m_octaves, &shifter->m_crispness, &shifter->m_formant, - &shifter->m_fast, + &shifter->m_wetDry, &shifter->m_input[0], &shifter->m_output[0], &shifter->m_input[1], @@ -328,8 +329,17 @@ RubberBandPitchShifter::connectPort(LADSPA_Handle handle, *ports[port] = (float *)location; if (shifter->m_latency) { - *(shifter->m_latency) = - float(shifter->m_stretcher->getLatency() + shifter->m_reserve); + *(shifter->m_latency) = shifter->getLatency(); + } +} + +int +RubberBandPitchShifter::getLatency() const +{ + if (m_stretcher) { + return m_stretcher->getLatency() + m_reserve; + } else { + return 0; } } @@ -353,6 +363,11 @@ RubberBandPitchShifter::activateImpl() m_outputBuffer[c]->zero(m_reserve); } + for (size_t c = 0; c < m_channels; ++c) { + m_delayMixBuffer[c]->reset(); + m_delayMixBuffer[c]->zero(getLatency()); + } + m_minfill = 0; // prime stretcher @@ -431,23 +446,6 @@ RubberBandPitchShifter::updateFormant() m_currentFormant = f; } -void -RubberBandPitchShifter::updateFast() -{ - if (!m_fast) return; - - bool f = (*m_fast > 0.5f); - if (f == m_currentFast) return; - - RubberBandStretcher *s = m_stretcher; - - s->setPitchOption(f ? - RubberBandStretcher::OptionPitchHighSpeed : - RubberBandStretcher::OptionPitchHighConsistency); - - m_currentFast = f; -} - void RubberBandPitchShifter::runImpl(unsigned long insamples) { @@ -466,6 +464,24 @@ RubberBandPitchShifter::runImpl(unsigned long insamples) offset += block; } + + if (m_wetDry) { + for (size_t c = 0; c < m_channels; ++c) { + m_delayMixBuffer[c]->write(m_input[c], insamples); + } + float mix = *m_wetDry; + for (size_t c = 0; c < m_channels; ++c) { + if (mix > 0.0) { + for (unsigned long i = 0; i < insamples; ++i) { + float dry = m_delayMixBuffer[c]->readOne(); + m_output[c][i] *= (1.0 - mix); + m_output[c][i] += dry * mix; + } + } else { + m_delayMixBuffer[c]->skip(insamples); + } + } + } } void @@ -480,15 +496,19 @@ RubberBandPitchShifter::runImpl(unsigned long insamples, unsigned long offset) m_stretcher->setPitchScale(m_ratio); m_prevRatio = m_ratio; } - +/* if (m_latency) { - *m_latency = float(m_stretcher->getLatency() + m_reserve); -// cerr << "latency = " << *m_latency << endl; + float latencyWas = *m_latency; + *m_latency = getLatency(); + cerr << "latency = " << *m_latency << endl; + if (*m_latency != latencyWas) { + cerr << "NOTE: latency changed from " << latencyWas + << " to " << *m_latency << endl; + } } - +*/ updateCrispness(); updateFormant(); - updateFast(); const int samples = insamples; int processed = 0; @@ -496,17 +516,6 @@ RubberBandPitchShifter::runImpl(unsigned long insamples, unsigned long offset) float *ptrs[2]; - int rs = m_outputBuffer[0]->getReadSpace(); - if (rs < int(m_minfill)) { -// cerr << "temporary expansion (have " << rs << ", want " << m_reserve << ")" << endl; - m_stretcher->setTimeRatio(1.1); // fill up temporarily - } else if (rs > 8192) { -// cerr << "temporary reduction (have " << rs << ", want " << m_reserve << ")" << endl; - m_stretcher->setTimeRatio(0.9); // reduce temporarily - } else { - m_stretcher->setTimeRatio(1.0); - } - while (processed < samples) { // never feed more than the minimum necessary number of @@ -523,24 +532,18 @@ RubberBandPitchShifter::runImpl(unsigned long insamples, unsigned long offset) int avail = m_stretcher->available(); int writable = m_outputBuffer[0]->getWriteSpace(); - int outchunk = min(avail, writable); + + int outchunk = avail; + if (outchunk > writable) { + cerr << "RubberBandPitchShifter::runImpl: buffer is not large enough: chunk = " << outchunk << ", space = " << writable << endl; + outchunk = writable; + } + size_t actual = m_stretcher->retrieve(m_scratch, outchunk); outTotal += actual; -// incount += inchunk; -// outcount += actual; - -// cout << "avail: " << avail << ", outchunk = " << outchunk; -// if (actual != outchunk) cout << " (" << actual << ")"; -// cout << endl; - - outchunk = actual; - for (size_t c = 0; c < m_channels; ++c) { - if (int(m_outputBuffer[c]->getWriteSpace()) < outchunk) { - cerr << "RubberBandPitchShifter::runImpl: buffer overrun: chunk = " << outchunk << ", space = " << m_outputBuffer[c]->getWriteSpace() << endl; - } - m_outputBuffer[c]->write(m_scratch[c], outchunk); + m_outputBuffer[c]->write(m_scratch[c], actual); } } @@ -555,7 +558,7 @@ RubberBandPitchShifter::runImpl(unsigned long insamples, unsigned long offset) if (m_minfill == 0) { m_minfill = m_outputBuffer[0]->getReadSpace(); -// cerr << "minfill = " << m_minfill << endl; + cerr << "minfill = " << m_minfill << endl; } } diff --git a/ladspa/RubberBandPitchShifter.h b/ladspa/RubberBandPitchShifter.h index 7f56e11..4214fff 100644 --- a/ladspa/RubberBandPitchShifter.h +++ b/ladspa/RubberBandPitchShifter.h @@ -48,7 +48,7 @@ protected: CentsPort = 3, CrispnessPort = 4, FormantPort = 5, - FastPort = 6, + WetDryPort = 6, InputPort1 = 7, OutputPort1 = 8, PortCountMono = OutputPort1 + 1, @@ -83,7 +83,8 @@ protected: void updateRatio(); void updateCrispness(); void updateFormant(); - void updateFast(); + + int getLatency() const; float **m_input; float **m_output; @@ -93,12 +94,11 @@ protected: float *m_octaves; float *m_crispness; float *m_formant; - float *m_fast; + float *m_wetDry; double m_ratio; double m_prevRatio; int m_currentCrispness; bool m_currentFormant; - bool m_currentFast; size_t m_blockSize; size_t m_reserve; @@ -106,6 +106,7 @@ protected: RubberBand::RubberBandStretcher *m_stretcher; RubberBand::RingBuffer **m_outputBuffer; + RubberBand::RingBuffer **m_delayMixBuffer; float **m_scratch; int m_sampleRate; diff --git a/src/StretchCalculator.cpp b/src/StretchCalculator.cpp index 230cbd8..6359e4e 100644 --- a/src/StretchCalculator.cpp +++ b/src/StretchCalculator.cpp @@ -469,7 +469,7 @@ StretchCalculator::calculateSingle(double timeRatio, if (divergence > 1000 || divergence < -1000) { recovery = divergence / ((m_sampleRate / 10.0) / increment); } else if (divergence > 100 || divergence < -100) { - recovery = divergence / ((m_sampleRate / 25.0) / increment); + recovery = divergence / ((m_sampleRate / 20.0) / increment); } else { recovery = divergence / 4.0; } diff --git a/src/StretcherImpl.cpp b/src/StretcherImpl.cpp index d545c8c..f35e48f 100644 --- a/src/StretcherImpl.cpp +++ b/src/StretcherImpl.cpp @@ -868,7 +868,8 @@ size_t RubberBandStretcher::Impl::getLatency() const { if (!m_realtime) return 0; - return int((m_aWindowSize/2) / m_pitchScale + 1); + return lrint((m_aWindowSize/2) / m_pitchScale); +// return int(m_aWindowSize/2); } void diff --git a/src/StretcherProcess.cpp b/src/StretcherProcess.cpp index 5bdf80c..0d7d162 100644 --- a/src/StretcherProcess.cpp +++ b/src/StretcherProcess.cpp @@ -1154,11 +1154,11 @@ RubberBandStretcher::Impl::writeOutput(RingBuffer &to, float *from, size_ // configure(), so we don't want to remove any here. //!!! size_t startSkip = 0; -// if (!m_realtime) { + if (!m_realtime) { //!!! lock down the latency to this initial value in RT mode startSkip = lrintf((m_sWindowSize/2) / m_pitchScale); // startSkip = m_sWindowSize/2; -// } + } if (outCount > startSkip) { From a3aff961e43184ba6d6ac8cb709ef45b53636488 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 14 May 2021 14:24:54 +0100 Subject: [PATCH 12/32] Avoid identifying transients when the divergence is particularly high already, so as not to pile on further --- src/StretchCalculator.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/StretchCalculator.cpp b/src/StretchCalculator.cpp index 6359e4e..4f4a304 100644 --- a/src/StretchCalculator.cpp +++ b/src/StretchCalculator.cpp @@ -432,7 +432,13 @@ StretchCalculator::calculateSingle(double timeRatio, // if (ratio > 1) transientThreshold = 0.25f; if (m_useHardPeaks && df > m_prevDf * 1.1f && df > transientThreshold) { - isTransient = true; + if (divergence > 1000 || divergence < -1000) { + if (m_debugLevel > 1) { + std::cerr << "StretchCalculator::calculateSingle: transient, but we're not permitting it because the divergence (" << divergence << ") is too great" << std::endl; + } + } else { + isTransient = true; + } } if (m_debugLevel > 2) { @@ -479,7 +485,7 @@ StretchCalculator::calculateSingle(double timeRatio, std::cerr << "divergence = " << divergence << ", recovery = " << recovery << ", incr = " << incr << ", "; } - int minIncr = lrint(increment * ratio * 0.5); + int minIncr = lrint(increment * ratio * 0.3); int maxIncr = lrint(increment * ratio * 2); if (incr < minIncr) { From a218ad42faf25d5c2478496b1cd6b88e05c7b254 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 14 May 2021 14:26:52 +0100 Subject: [PATCH 13/32] Constrain to +/- 2 octaves (+ semitones) as it's less demanding of block size; minor tidying --- ladspa/RubberBandPitchShifter.cpp | 41 ++++++++++++++++++++----------- ladspa/RubberBandPitchShifter.h | 1 + src/StretcherImpl.cpp | 20 ++++++++------- src/StretcherProcess.cpp | 6 ++--- 4 files changed, 42 insertions(+), 26 deletions(-) diff --git a/ladspa/RubberBandPitchShifter.cpp b/ladspa/RubberBandPitchShifter.cpp index 3ca1245..78c7c03 100644 --- a/ladspa/RubberBandPitchShifter.cpp +++ b/ladspa/RubberBandPitchShifter.cpp @@ -112,7 +112,7 @@ RubberBandPitchShifter::hintsMono[PortCountMono] = LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_INTEGER, - -3.0, 3.0 }, + -2.0, 2.0 }, { LADSPA_HINT_DEFAULT_MAXIMUM | // crispness LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | @@ -148,7 +148,7 @@ RubberBandPitchShifter::hintsStereo[PortCountStereo] = LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_INTEGER, - -3.0, 3.0 }, + -2.0, 2.0 }, { LADSPA_HINT_DEFAULT_MAXIMUM | // crispness LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | @@ -241,7 +241,8 @@ RubberBandPitchShifter::RubberBandPitchShifter(int sampleRate, size_t channels) m_currentCrispness(-1), m_currentFormant(false), m_blockSize(1024), - m_reserve(1024), + m_reserve(8192), + m_bufsize(0), m_minfill(0), m_stretcher(new RubberBandStretcher (sampleRate, channels, @@ -257,18 +258,20 @@ RubberBandPitchShifter::RubberBandPitchShifter(int sampleRate, size_t channels) m_delayMixBuffer = new RingBuffer *[m_channels]; m_scratch = new float *[m_channels]; + m_bufsize = m_blockSize + m_reserve + 8192; + for (size_t c = 0; c < m_channels; ++c) { m_input[c] = 0; m_output[c] = 0; - int bufsize = m_blockSize + m_reserve + 8192; + m_outputBuffer[c] = new RingBuffer(m_bufsize); + m_delayMixBuffer[c] = new RingBuffer(m_bufsize); - m_outputBuffer[c] = new RingBuffer(bufsize); - m_delayMixBuffer[c] = new RingBuffer(bufsize); - - m_scratch[c] = new float[bufsize]; - for (int i = 0; i < bufsize; ++i) m_scratch[c][i] = 0.f; + m_scratch[c] = new float[m_bufsize]; + for (int i = 0; i < m_bufsize; ++i) { + m_scratch[c][i] = 0.f; + } } activateImpl(); @@ -367,6 +370,12 @@ RubberBandPitchShifter::activateImpl() m_delayMixBuffer[c]->reset(); m_delayMixBuffer[c]->zero(getLatency()); } + + for (size_t c = 0; c < m_channels; ++c) { + for (int i = 0; i < m_bufsize; ++i) { + m_scratch[c][i] = 0.f; + } + } m_minfill = 0; @@ -496,7 +505,7 @@ RubberBandPitchShifter::runImpl(unsigned long insamples, unsigned long offset) m_stretcher->setPitchScale(m_ratio); m_prevRatio = m_ratio; } -/* + if (m_latency) { float latencyWas = *m_latency; *m_latency = getLatency(); @@ -506,7 +515,7 @@ RubberBandPitchShifter::runImpl(unsigned long insamples, unsigned long offset) << " to " << *m_latency << endl; } } -*/ + updateCrispness(); updateFormant(); @@ -535,7 +544,7 @@ RubberBandPitchShifter::runImpl(unsigned long insamples, unsigned long offset) int outchunk = avail; if (outchunk > writable) { - cerr << "RubberBandPitchShifter::runImpl: buffer is not large enough: chunk = " << outchunk << ", space = " << writable << endl; + cerr << "RubberBandPitchShifter::runImpl: buffer is not large enough: size = " << m_outputBuffer[0]->getSize() << ", chunk = " << outchunk << ", space = " << writable << " (buffer contains " << m_outputBuffer[0]->getReadSpace() << " unread)" << endl; outchunk = writable; } @@ -556,8 +565,12 @@ RubberBandPitchShifter::runImpl(unsigned long insamples, unsigned long offset) m_outputBuffer[c]->read(&(m_output[c][offset]), chunk); } - if (m_minfill == 0) { - m_minfill = m_outputBuffer[0]->getReadSpace(); + int fill = m_outputBuffer[0]->getReadSpace(); + +// cerr << "fill = " << fill << endl; + + if (fill < m_minfill || m_minfill == 0) { + m_minfill = fill; cerr << "minfill = " << m_minfill << endl; } } diff --git a/ladspa/RubberBandPitchShifter.h b/ladspa/RubberBandPitchShifter.h index 4214fff..b954ccd 100644 --- a/ladspa/RubberBandPitchShifter.h +++ b/ladspa/RubberBandPitchShifter.h @@ -102,6 +102,7 @@ protected: size_t m_blockSize; size_t m_reserve; + size_t m_bufsize; size_t m_minfill; RubberBand::RubberBandStretcher *m_stretcher; diff --git a/src/StretcherImpl.cpp b/src/StretcherImpl.cpp index f35e48f..7869d08 100644 --- a/src/StretcherImpl.cpp +++ b/src/StretcherImpl.cpp @@ -64,7 +64,7 @@ const size_t RubberBandStretcher::Impl::m_defaultFftSize = 2048; int -RubberBandStretcher::Impl::m_defaultDebugLevel = 0; +RubberBandStretcher::Impl::m_defaultDebugLevel = 2; static bool _initialised = false; @@ -544,8 +544,8 @@ RubberBandStretcher::Impl::calculateSizes() // ratio) for any chunk. if (m_debugLevel > 0) { - cerr << "configure: time ratio = " << m_timeRatio << ", pitch scale = " << m_pitchScale << ", effective ratio = " << getEffectiveRatio() << endl; - cerr << "configure: analysis window size = " << m_aWindowSize << ", synthesis window size = " << m_sWindowSize << ", fft size = " << m_fftSize << ", increment = " << m_increment << " (approx output increment = " << int(lrint(m_increment * getEffectiveRatio())) << ")" << endl; + cerr << "calculateSizes: time ratio = " << m_timeRatio << ", pitch scale = " << m_pitchScale << ", effective ratio = " << getEffectiveRatio() << endl; + cerr << "calculateSizes: analysis window size = " << m_aWindowSize << ", synthesis window size = " << m_sWindowSize << ", fft size = " << m_fftSize << ", increment = " << m_increment << " (approx output increment = " << int(lrint(m_increment * getEffectiveRatio())) << ")" << endl; } if (std::max(m_aWindowSize, m_sWindowSize) > m_maxProcessSize) { @@ -575,15 +575,17 @@ RubberBandStretcher::Impl::calculateSizes() } if (m_debugLevel > 0) { - cerr << "configure: outbuf size = " << m_outbufSize << endl; + cerr << "calculateSizes: outbuf size = " << m_outbufSize << endl; } } void RubberBandStretcher::Impl::configure() { -// std::cerr << "configure[" << this << "]: realtime = " << m_realtime << ", pitch scale = " -// << m_pitchScale << ", channels = " << m_channels << std::endl; + if (m_debugLevel > 0) { + std::cerr << "configure[" << this << "]: realtime = " << m_realtime << ", pitch scale = " + << m_pitchScale << ", channels = " << m_channels << std::endl; + } size_t prevFftSize = m_fftSize; size_t prevAWindowSize = m_aWindowSize; @@ -745,7 +747,7 @@ RubberBandStretcher::Impl::configure() // if (!m_realtime) { if (m_debugLevel > 1) { - cerr << "Not real time mode: prefilling" << endl; + cerr << "Not real time mode: prefilling with " << m_aWindowSize/2 << " samples" << endl; } for (size_t c = 0; c < m_channels; ++c) { m_channelData[c]->reset(); @@ -1373,12 +1375,12 @@ RubberBandStretcher::Impl::process(const float *const *input, size_t samples, bo } #endif - if (m_debugLevel > 2) { + if (m_debugLevel > 1) { if (!allConsumed) cerr << "process looping" << endl; } } - if (m_debugLevel > 2) { + if (m_debugLevel > 1) { cerr << "process returning" << endl; } diff --git a/src/StretcherProcess.cpp b/src/StretcherProcess.cpp index 0d7d162..87ab89c 100644 --- a/src/StretcherProcess.cpp +++ b/src/StretcherProcess.cpp @@ -284,7 +284,7 @@ RubberBandStretcher::Impl::processChunks(size_t c, bool &any, bool &last) while (!last) { if (!testInbufReadSpace(c)) { - if (m_debugLevel > 2) { + if (m_debugLevel > 1) { cerr << "processChunks: out of input" << endl; } break; @@ -349,7 +349,7 @@ RubberBandStretcher::Impl::processOneChunk() for (size_t c = 0; c < m_channels; ++c) { if (!testInbufReadSpace(c)) { - if (m_debugLevel > 2) { + if (m_debugLevel > 1) { cerr << "processOneChunk: out of input" << endl; } return false; @@ -404,7 +404,7 @@ RubberBandStretcher::Impl::testInbufReadSpace(size_t c) if (!m_threaded) { #endif if (m_debugLevel > 1) { - cerr << "WARNING: RubberBandStretcher: read space < chunk size (" + cerr << "Note: RubberBandStretcher: read space < chunk size (" << inbuf.getReadSpace() << " < " << m_aWindowSize << ") when not all input written, on processChunks for channel " << c << endl; } From 20d3f5ed6a5fda21b2ccc3cd6b5d8b65a27864b0 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 14 May 2021 15:09:28 +0100 Subject: [PATCH 14/32] Further timing fixes --- ladspa/RubberBandPitchShifter.cpp | 38 +++++-------------------------- 1 file changed, 6 insertions(+), 32 deletions(-) diff --git a/ladspa/RubberBandPitchShifter.cpp b/ladspa/RubberBandPitchShifter.cpp index 78c7c03..ae3e45f 100644 --- a/ladspa/RubberBandPitchShifter.cpp +++ b/ladspa/RubberBandPitchShifter.cpp @@ -269,7 +269,7 @@ RubberBandPitchShifter::RubberBandPitchShifter(int sampleRate, size_t channels) m_delayMixBuffer[c] = new RingBuffer(m_bufsize); m_scratch[c] = new float[m_bufsize]; - for (int i = 0; i < m_bufsize; ++i) { + for (size_t i = 0; i < m_bufsize; ++i) { m_scratch[c][i] = 0.f; } } @@ -339,11 +339,7 @@ RubberBandPitchShifter::connectPort(LADSPA_Handle handle, int RubberBandPitchShifter::getLatency() const { - if (m_stretcher) { - return m_stretcher->getLatency() + m_reserve; - } else { - return 0; - } + return m_reserve; } void @@ -363,7 +359,6 @@ RubberBandPitchShifter::activateImpl() for (size_t c = 0; c < m_channels; ++c) { m_outputBuffer[c]->reset(); - m_outputBuffer[c]->zero(m_reserve); } for (size_t c = 0; c < m_channels; ++c) { @@ -372,22 +367,14 @@ RubberBandPitchShifter::activateImpl() } for (size_t c = 0; c < m_channels; ++c) { - for (int i = 0; i < m_bufsize; ++i) { + for (size_t i = 0; i < m_bufsize; ++i) { m_scratch[c][i] = 0.f; } } m_minfill = 0; - // prime stretcher -// for (int i = 0; i < 8; ++i) { -// int reqd = m_stretcher->getSamplesRequired(); -// m_stretcher->process(m_scratch, reqd, false); -// int avail = m_stretcher->available(); -// if (avail > 0) { -// m_stretcher->retrieve(m_scratch, avail); -// } -// } + m_stretcher->process(m_scratch, m_reserve, false); } void @@ -496,10 +483,6 @@ RubberBandPitchShifter::runImpl(unsigned long insamples) void RubberBandPitchShifter::runImpl(unsigned long insamples, unsigned long offset) { -// cerr << "RubberBandPitchShifter::runImpl(" << insamples << ")" << endl; - -// static int incount = 0, outcount = 0; - updateRatio(); if (m_ratio != m_prevRatio) { m_stretcher->setPitchScale(m_ratio); @@ -507,13 +490,7 @@ RubberBandPitchShifter::runImpl(unsigned long insamples, unsigned long offset) } if (m_latency) { - float latencyWas = *m_latency; *m_latency = getLatency(); - cerr << "latency = " << *m_latency << endl; - if (*m_latency != latencyWas) { - cerr << "NOTE: latency changed from " << latencyWas - << " to " << *m_latency << endl; - } } updateCrispness(); @@ -565,13 +542,10 @@ RubberBandPitchShifter::runImpl(unsigned long insamples, unsigned long offset) m_outputBuffer[c]->read(&(m_output[c][offset]), chunk); } - int fill = m_outputBuffer[0]->getReadSpace(); - -// cerr << "fill = " << fill << endl; - + size_t fill = m_outputBuffer[0]->getReadSpace(); if (fill < m_minfill || m_minfill == 0) { m_minfill = fill; - cerr << "minfill = " << m_minfill << endl; +// cerr << "minfill = " << m_minfill << endl; } } From 27a2980a5624d117f07bff816c9af6b0911dcc31 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 14 May 2021 15:10:24 +0100 Subject: [PATCH 15/32] Restore original behaviour of pre-fill/start-skip --- src/StretcherImpl.cpp | 7 +++---- src/StretcherProcess.cpp | 4 +--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/StretcherImpl.cpp b/src/StretcherImpl.cpp index 7869d08..0794156 100644 --- a/src/StretcherImpl.cpp +++ b/src/StretcherImpl.cpp @@ -64,7 +64,7 @@ const size_t RubberBandStretcher::Impl::m_defaultFftSize = 2048; int -RubberBandStretcher::Impl::m_defaultDebugLevel = 2; +RubberBandStretcher::Impl::m_defaultDebugLevel = 0; static bool _initialised = false; @@ -740,12 +740,11 @@ RubberBandStretcher::Impl::configure() // number of onset detector chunks will be the number of audio // samples input, divided by the input increment, plus one. - //!!! // In real-time mode, we don't do this prefill -- it's better to // start with a swoosh than introduce more latency, and we don't // want gaps when the ratio changes. -// if (!m_realtime) { + if (!m_realtime) { if (m_debugLevel > 1) { cerr << "Not real time mode: prefilling with " << m_aWindowSize/2 << " samples" << endl; } @@ -753,7 +752,7 @@ RubberBandStretcher::Impl::configure() m_channelData[c]->reset(); m_channelData[c]->inbuf->zero(m_aWindowSize/2); } -// } + } } diff --git a/src/StretcherProcess.cpp b/src/StretcherProcess.cpp index 87ab89c..cf6f545 100644 --- a/src/StretcherProcess.cpp +++ b/src/StretcherProcess.cpp @@ -1152,12 +1152,10 @@ RubberBandStretcher::Impl::writeOutput(RingBuffer &to, float *from, size_ // samples, because the first chunk is centred on the start of the // output. In RT mode we didn't apply any pre-padding in // configure(), so we don't want to remove any here. -//!!! + size_t startSkip = 0; if (!m_realtime) { - //!!! lock down the latency to this initial value in RT mode startSkip = lrintf((m_sWindowSize/2) / m_pitchScale); -// startSkip = m_sWindowSize/2; } if (outCount > startSkip) { From d22cbf3098d4ee03f321b1c77b2bfa2f3edf3129 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 14 May 2021 15:25:54 +0100 Subject: [PATCH 16/32] Comments --- src/dsp/Resampler.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/dsp/Resampler.h b/src/dsp/Resampler.h index 775fda7..bfed5cd 100644 --- a/src/dsp/Resampler.h +++ b/src/dsp/Resampler.h @@ -146,10 +146,25 @@ public: double ratio, bool final = false); + /** + * Return the channel count provided on construction. + */ int getChannelCount() const; + /** + * Return the ratio that will be actually used when the given + * ratio is requested. For example, if the resampler internally + * uses a rational approximation of the given ratio, this will + * return the closest double to that approximation. Not all + * implementations support this; an implementation that does not + * will just return the given ratio. + */ double getEffectiveRatio(double ratio) const; + /** + * Reset the internal processing state so that the next call + * behaves as if the resampler had just been constructed. + */ void reset(); class Impl; From 3bcb2de31b3b1fe0f98215098f4d514f55a2e1af Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Fri, 14 May 2021 16:05:58 +0100 Subject: [PATCH 17/32] Wording tweak --- main/main.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main/main.cpp b/main/main.cpp index 6198e22..a47638c 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -259,9 +259,9 @@ int main(int argc, char **argv) cerr << endl; cerr << " --freqmap Use file F as the source for frequency map" << endl; cerr << endl; - cerr << " As --pitchmap, except that the second column in the file contains frequency" << endl; - cerr << " multipliers rather than pitch offsets (the same as the difference between" << endl; - cerr << " pitch and frequency options above)." << endl; + cerr << " A frequency map file is like a pitch map, except that its second column" << endl; + cerr << " lists frequency multipliers rather than pitch offsets (like the difference" << endl; + cerr << " between pitch and frequency options above)." << endl; cerr << endl; cerr << "The following options provide a simple way to adjust the sound. See below" << endl; cerr << "for more details." << endl; From 66354796b8432822b12d3df39e955026a888206a Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 30 Sep 2021 15:36:21 +0100 Subject: [PATCH 18/32] Pull in multi-channel resampler fix (not directly relevant to RB) --- src/dsp/BQResampler.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dsp/BQResampler.cpp b/src/dsp/BQResampler.cpp index 0804a28..68323ae 100644 --- a/src/dsp/BQResampler.cpp +++ b/src/dsp/BQResampler.cpp @@ -590,7 +590,9 @@ BQResampler::reconstruct_one(state *s) const int phase_length = pr.length; double result = 0.0; - int dot_length = min(phase_length, int(s->buffer.size()) - s->left); + int dot_length = + min(phase_length, + (int(s->buffer.size()) - s->left) / m_channels); if (m_dynamism == RatioMostlyFixed) { int phase_start = pr.start_index; From c8ff7c3b2b89ad3b2f4087ee4320103478439b0d Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 4 Oct 2021 10:56:00 +0100 Subject: [PATCH 19/32] Fall back to builtin resampler, no longer speex --- meson.build | 4 ++-- meson_options.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/meson.build b/meson.build index 7871f9e..8e993dc 100644 --- a/meson.build +++ b/meson.build @@ -140,7 +140,7 @@ if resampler == 'auto' if samplerate_dep.found() resampler = 'libsamplerate' else - resampler = 'speex' + resampler = 'builtin' endif endif @@ -447,7 +447,7 @@ if not get_option('no_shared') message('Will build Rubber Band Library shared library') rubberband_dynamic = shared_library( rubberband_dynamic_name, - objects: rubberband_static.extract_all_objects(), + objects: rubberband_static.extract_all_objects(recursive: true), link_args: [ arch_flags, feature_libraries, diff --git a/meson_options.txt b/meson_options.txt index 8e65fa0..2f13324 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -9,7 +9,7 @@ option('resampler', type: 'combo', choices: ['auto', 'builtin', 'libsamplerate', 'speex', 'ipp'], value: 'auto', - description: 'Resampler library to use. Recommended is libsamplerate. The default (auto) will use libsamplerate if available, speex otherwise.') + description: 'Resampler library to use. The default (auto) will use libsamplerate if available, the builtin implementation otherwise.') option('ipp_path', type: 'string', From 61f7b5f8e69785462a7761075c7192f3df254121 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 4 Oct 2021 12:21:39 +0100 Subject: [PATCH 20/32] Update includes so they don't require include path to be set --- main/main.cpp | 2 +- src/audiocurves/CompoundAudioCurve.cpp | 2 +- src/audiocurves/CompoundAudioCurve.h | 3 +-- src/audiocurves/ConstantAudioCurve.h | 2 +- src/audiocurves/HighFrequencyAudioCurve.h | 2 +- src/audiocurves/PercussiveAudioCurve.cpp | 4 ++-- src/audiocurves/PercussiveAudioCurve.h | 2 +- src/audiocurves/SilentAudioCurve.h | 2 +- src/audiocurves/SpectralDifferenceAudioCurve.cpp | 4 ++-- src/audiocurves/SpectralDifferenceAudioCurve.h | 4 ++-- src/base/Profiler.cpp | 2 +- src/base/Profiler.h | 2 +- src/base/RingBuffer.h | 4 ++-- src/base/Scavenger.h | 6 +++--- src/dsp/AudioCurveCalculator.h | 3 +-- src/dsp/BQResampler.cpp | 4 ++-- src/dsp/BQResampler.h | 4 ++-- src/dsp/FFT.cpp | 10 +++++----- src/dsp/FFT.h | 2 +- src/dsp/MovingMedian.h | 2 +- src/dsp/Resampler.cpp | 4 ++-- src/dsp/Resampler.h | 2 +- src/dsp/SincWindow.h | 6 +++--- src/dsp/Window.h | 6 +++--- 24 files changed, 41 insertions(+), 43 deletions(-) diff --git a/main/main.cpp b/main/main.cpp index a47638c..eb25e4b 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -834,7 +834,7 @@ int main(int argc, char **argv) cerr << "elapsed time: " << sec << " sec, in frames/sec: " << countIn/sec << ", out frames/sec: " << countOut/sec << endl; } - RubberBand::Profiler::dump(); + Profiler::dump(); return 0; } diff --git a/src/audiocurves/CompoundAudioCurve.cpp b/src/audiocurves/CompoundAudioCurve.cpp index 122de96..45e6926 100644 --- a/src/audiocurves/CompoundAudioCurve.cpp +++ b/src/audiocurves/CompoundAudioCurve.cpp @@ -23,7 +23,7 @@ #include "CompoundAudioCurve.h" -#include "dsp/MovingMedian.h" +#include "../dsp/MovingMedian.h" #include diff --git a/src/audiocurves/CompoundAudioCurve.h b/src/audiocurves/CompoundAudioCurve.h index 123f8a6..0204aa2 100644 --- a/src/audiocurves/CompoundAudioCurve.h +++ b/src/audiocurves/CompoundAudioCurve.h @@ -24,10 +24,9 @@ #ifndef RUBBERBAND_COMPOUND_AUDIO_CURVE_H #define RUBBERBAND_COMPOUND_AUDIO_CURVE_H -#include "dsp/AudioCurveCalculator.h" #include "PercussiveAudioCurve.h" #include "HighFrequencyAudioCurve.h" -#include "dsp/SampleFilter.h" +#include "../dsp/SampleFilter.h" namespace RubberBand { diff --git a/src/audiocurves/ConstantAudioCurve.h b/src/audiocurves/ConstantAudioCurve.h index a97ac4e..63df9a4 100644 --- a/src/audiocurves/ConstantAudioCurve.h +++ b/src/audiocurves/ConstantAudioCurve.h @@ -24,7 +24,7 @@ #ifndef RUBBERBAND_CONSTANT_AUDIO_CURVE_H #define RUBBERBAND_CONSTANT_AUDIO_CURVE_H -#include "dsp/AudioCurveCalculator.h" +#include "../dsp/AudioCurveCalculator.h" namespace RubberBand { diff --git a/src/audiocurves/HighFrequencyAudioCurve.h b/src/audiocurves/HighFrequencyAudioCurve.h index 34db579..80fadd3 100644 --- a/src/audiocurves/HighFrequencyAudioCurve.h +++ b/src/audiocurves/HighFrequencyAudioCurve.h @@ -24,7 +24,7 @@ #ifndef RUBBERBAND_HIGHFREQUENCY_AUDIO_CURVE_H #define RUBBERBAND_HIGHFREQUENCY_AUDIO_CURVE_H -#include "dsp/AudioCurveCalculator.h" +#include "../dsp/AudioCurveCalculator.h" namespace RubberBand { diff --git a/src/audiocurves/PercussiveAudioCurve.cpp b/src/audiocurves/PercussiveAudioCurve.cpp index 8b63e34..50c3907 100644 --- a/src/audiocurves/PercussiveAudioCurve.cpp +++ b/src/audiocurves/PercussiveAudioCurve.cpp @@ -23,8 +23,8 @@ #include "PercussiveAudioCurve.h" -#include "system/Allocators.h" -#include "system/VectorOps.h" +#include "../system/Allocators.h" +#include "../system/VectorOps.h" #include #include diff --git a/src/audiocurves/PercussiveAudioCurve.h b/src/audiocurves/PercussiveAudioCurve.h index 645d1dd..593726f 100644 --- a/src/audiocurves/PercussiveAudioCurve.h +++ b/src/audiocurves/PercussiveAudioCurve.h @@ -24,7 +24,7 @@ #ifndef RUBBERBAND_PERCUSSIVE_AUDIO_CURVE_H #define RUBBERBAND_PERCUSSIVE_AUDIO_CURVE_H -#include "dsp/AudioCurveCalculator.h" +#include "../dsp/AudioCurveCalculator.h" namespace RubberBand { diff --git a/src/audiocurves/SilentAudioCurve.h b/src/audiocurves/SilentAudioCurve.h index 36b7984..501340c 100644 --- a/src/audiocurves/SilentAudioCurve.h +++ b/src/audiocurves/SilentAudioCurve.h @@ -24,7 +24,7 @@ #ifndef RUBBERBAND_SILENT_AUDIO_CURVE_H #define RUBBERBAND_SILENT_AUDIO_CURVE_H -#include "dsp/AudioCurveCalculator.h" +#include "../dsp/AudioCurveCalculator.h" namespace RubberBand { diff --git a/src/audiocurves/SpectralDifferenceAudioCurve.cpp b/src/audiocurves/SpectralDifferenceAudioCurve.cpp index 5579cc0..60f6e46 100644 --- a/src/audiocurves/SpectralDifferenceAudioCurve.cpp +++ b/src/audiocurves/SpectralDifferenceAudioCurve.cpp @@ -23,8 +23,8 @@ #include "SpectralDifferenceAudioCurve.h" -#include "system/Allocators.h" -#include "system/VectorOps.h" +#include "../system/Allocators.h" +#include "../system/VectorOps.h" namespace RubberBand { diff --git a/src/audiocurves/SpectralDifferenceAudioCurve.h b/src/audiocurves/SpectralDifferenceAudioCurve.h index 66b6133..7d79b79 100644 --- a/src/audiocurves/SpectralDifferenceAudioCurve.h +++ b/src/audiocurves/SpectralDifferenceAudioCurve.h @@ -24,8 +24,8 @@ #ifndef RUBBERBAND_SPECTRALDIFFERENCE_AUDIO_CURVE_H #define RUBBERBAND_SPECTRALDIFFERENCE_AUDIO_CURVE_H -#include "dsp/AudioCurveCalculator.h" -#include "dsp/Window.h" +#include "../dsp/AudioCurveCalculator.h" +#include "../dsp/Window.h" namespace RubberBand { diff --git a/src/base/Profiler.cpp b/src/base/Profiler.cpp index d96b571..a7ed6c2 100644 --- a/src/base/Profiler.cpp +++ b/src/base/Profiler.cpp @@ -23,7 +23,7 @@ #include "Profiler.h" -#include "system/Thread.h" +#include "../system/Thread.h" #include #include diff --git a/src/base/Profiler.h b/src/base/Profiler.h index bc39107..b9280cb 100644 --- a/src/base/Profiler.h +++ b/src/base/Profiler.h @@ -42,7 +42,7 @@ #ifdef PROFILE_CLOCKS #include #else -#include "system/sysutils.h" +#include "../system/sysutils.h" #ifndef _WIN32 #include #endif diff --git a/src/base/RingBuffer.h b/src/base/RingBuffer.h index cd9df19..152fc6b 100644 --- a/src/base/RingBuffer.h +++ b/src/base/RingBuffer.h @@ -28,8 +28,8 @@ //#define DEBUG_RINGBUFFER 1 -#include "system/sysutils.h" -#include "system/Allocators.h" +#include "../system/sysutils.h" +#include "../system/Allocators.h" #include diff --git a/src/base/Scavenger.h b/src/base/Scavenger.h index e8b6b5b..b6c045b 100644 --- a/src/base/Scavenger.h +++ b/src/base/Scavenger.h @@ -33,9 +33,9 @@ #include #endif -#include "system/Thread.h" -#include "system/sysutils.h" -#include "system/Allocators.h" +#include "../system/Thread.h" +#include "../system/sysutils.h" +#include "../system/Allocators.h" //#define DEBUG_SCAVENGER 1 diff --git a/src/dsp/AudioCurveCalculator.h b/src/dsp/AudioCurveCalculator.h index 0b47cd0..9b8dc61 100644 --- a/src/dsp/AudioCurveCalculator.h +++ b/src/dsp/AudioCurveCalculator.h @@ -26,8 +26,7 @@ #include - -#include "system/sysutils.h" +#include "../system/sysutils.h" namespace RubberBand { diff --git a/src/dsp/BQResampler.cpp b/src/dsp/BQResampler.cpp index 68323ae..c7db9c4 100644 --- a/src/dsp/BQResampler.cpp +++ b/src/dsp/BQResampler.cpp @@ -27,8 +27,8 @@ #include -#include "system/Allocators.h" -#include "system/VectorOps.h" +#include "../system/Allocators.h" +#include "../system/VectorOps.h" #define BQ_R__ R__ diff --git a/src/dsp/BQResampler.h b/src/dsp/BQResampler.h index 9ca0603..8a441f8 100644 --- a/src/dsp/BQResampler.h +++ b/src/dsp/BQResampler.h @@ -26,8 +26,8 @@ #include -#include "system/Allocators.h" -#include "system/VectorOps.h" +#include "../system/Allocators.h" +#include "../system/VectorOps.h" namespace RubberBand { diff --git a/src/dsp/FFT.cpp b/src/dsp/FFT.cpp index 5bfc3f7..57c33e6 100644 --- a/src/dsp/FFT.cpp +++ b/src/dsp/FFT.cpp @@ -22,11 +22,11 @@ */ #include "FFT.h" -#include "system/Thread.h" -#include "base/Profiler.h" -#include "system/Allocators.h" -#include "system/VectorOps.h" -#include "system/VectorOpsComplex.h" +#include "../system/Thread.h" +#include "../base/Profiler.h" +#include "../system/Allocators.h" +#include "../system/VectorOps.h" +#include "../system/VectorOpsComplex.h" // Define USE_FFTW_WISDOM if you are defining HAVE_FFTW3 and you want // to use FFTW_MEASURE mode with persistent wisdom files. This will diff --git a/src/dsp/FFT.h b/src/dsp/FFT.h index fbf36a0..42f619d 100644 --- a/src/dsp/FFT.h +++ b/src/dsp/FFT.h @@ -24,7 +24,7 @@ #ifndef RUBBERBAND_FFT_H #define RUBBERBAND_FFT_H -#include "system/sysutils.h" +#include "../system/sysutils.h" #include #include diff --git a/src/dsp/MovingMedian.h b/src/dsp/MovingMedian.h index 50b7eca..bd1b3ed 100644 --- a/src/dsp/MovingMedian.h +++ b/src/dsp/MovingMedian.h @@ -26,7 +26,7 @@ #include "SampleFilter.h" -#include "system/Allocators.h" +#include "../system/Allocators.h" #include diff --git a/src/dsp/Resampler.cpp b/src/dsp/Resampler.cpp index 13336f5..ecfe379 100644 --- a/src/dsp/Resampler.cpp +++ b/src/dsp/Resampler.cpp @@ -23,8 +23,8 @@ #include "Resampler.h" -#include "system/Allocators.h" -#include "system/VectorOps.h" +#include "../system/Allocators.h" +#include "../system/VectorOps.h" #include #include diff --git a/src/dsp/Resampler.h b/src/dsp/Resampler.h index bfed5cd..88a4b85 100644 --- a/src/dsp/Resampler.h +++ b/src/dsp/Resampler.h @@ -24,7 +24,7 @@ #ifndef RUBBERBAND_RESAMPLER_H #define RUBBERBAND_RESAMPLER_H -#include "system/sysutils.h" +#include "../system/sysutils.h" namespace RubberBand { diff --git a/src/dsp/SincWindow.h b/src/dsp/SincWindow.h index 43670ad..b9fe971 100644 --- a/src/dsp/SincWindow.h +++ b/src/dsp/SincWindow.h @@ -29,9 +29,9 @@ #include #include -#include "system/sysutils.h" -#include "system/VectorOps.h" -#include "system/Allocators.h" +#include "../system/sysutils.h" +#include "../system/VectorOps.h" +#include "../system/Allocators.h" namespace RubberBand { diff --git a/src/dsp/Window.h b/src/dsp/Window.h index 52893a9..52f356c 100644 --- a/src/dsp/Window.h +++ b/src/dsp/Window.h @@ -28,9 +28,9 @@ #include #include -#include "system/sysutils.h" -#include "system/VectorOps.h" -#include "system/Allocators.h" +#include "../system/sysutils.h" +#include "../system/VectorOps.h" +#include "../system/Allocators.h" namespace RubberBand { From c69ce90c2584c3945540cff25b2e1c7caedafa65 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 4 Oct 2021 12:22:08 +0100 Subject: [PATCH 21/32] Add single-compilation-unit file --- single/RubberBandSingle.cpp | 78 +++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 single/RubberBandSingle.cpp diff --git a/single/RubberBandSingle.cpp b/single/RubberBandSingle.cpp new file mode 100644 index 0000000..3885962 --- /dev/null +++ b/single/RubberBandSingle.cpp @@ -0,0 +1,78 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Rubber Band Library + An audio time-stretching and pitch-shifting library. + Copyright 2007-2021 Particular Programs Ltd. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. + + Alternatively, if you have a valid commercial licence for the + Rubber Band Library obtained by agreement with the copyright + holders, you may redistribute and/or modify it under the terms + described in that licence. + + If you wish to distribute code using the Rubber Band Library + under terms other than those of the GNU General Public License, + you must obtain a valid commercial licence before doing so. +*/ + +/* + RubberBandSingle.cpp + + This is a single-file compilation unit for Rubber Band Library. + + To use the library in your project without building it separately, + include one of the headers rubberband/RubberBandStretcher.h if + your project is C++ or rubberband/rubberband-c.h if you need the C + ABI, then add this single C++ source file to your build. + + Don't move this file into your source tree - keep it in the same + place relative to the other Rubber Band code, so that the relative + include paths work, and just add it to your list of build files. + + This produces a build using the built-in FFT and resampler + implementations, except on Apple platforms where the vDSP FFT is + used (and you will need the Accelerate framework when linking). If + you want any further customisation, consider using the full build + system and building a standalone library. +*/ + +#define USE_BQRESAMPLER 1 + +#define NO_TIMING 1 +#define NO_THREADING 1 +#define NO_THREAD_CHECKS 1 + +#if defined(__APPLE__) +#define HAVE_VDSP 1 +#else +#define USE_BUILTIN_FFT 1 +#endif + +#include "../src/audiocurves/CompoundAudioCurve.cpp" +#include "../src/audiocurves/SpectralDifferenceAudioCurve.cpp" +#include "../src/audiocurves/HighFrequencyAudioCurve.cpp" +#include "../src/audiocurves/SilentAudioCurve.cpp" +#include "../src/audiocurves/ConstantAudioCurve.cpp" +#include "../src/audiocurves/PercussiveAudioCurve.cpp" +#include "../src/base/Profiler.cpp" +#include "../src/dsp/AudioCurveCalculator.cpp" +#include "../src/dsp/FFT.cpp" +#include "../src/dsp/Resampler.cpp" +#include "../src/dsp/BQResampler.cpp" +#include "../src/system/Allocators.cpp" +#include "../src/system/sysutils.cpp" +#include "../src/system/Thread.cpp" +#include "../src/RubberBandStretcher.cpp" +#include "../src/StretchCalculator.cpp" +#include "../src/StretcherChannelData.cpp" +#include "../src/StretcherImpl.cpp" +#include "../src/StretcherProcess.cpp" + +#include "../src/rubberband-c.cpp" + From 205f050081f8c4903d5ee1c7c29bed664d114560 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 4 Oct 2021 12:24:50 +0100 Subject: [PATCH 22/32] Use relative paths here too --- main/main.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/main/main.cpp b/main/main.cpp index eb25e4b..72c47e0 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -21,7 +21,7 @@ you must obtain a valid commercial licence before doing so. */ -#include "rubberband/RubberBandStretcher.h" +#include "../rubberband/RubberBandStretcher.h" #include #include @@ -33,17 +33,17 @@ #include -#include "system/sysutils.h" +#include "../src/system/sysutils.h" #ifdef _MSC_VER -#include "getopt/getopt.h" +#include "../src/getopt/getopt.h" #else #include #include #include #endif -#include "base/Profiler.h" +#include "../src/base/Profiler.h" using namespace std; using namespace RubberBand; From 829a208fd024ded153c1e2231a98bad2101f6b9a Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 4 Oct 2021 12:24:59 +0100 Subject: [PATCH 23/32] Check single-compilation-unit build too --- otherbuilds/check.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/otherbuilds/check.sh b/otherbuilds/check.sh index 19aee02..d349837 100755 --- a/otherbuilds/check.sh +++ b/otherbuilds/check.sh @@ -4,10 +4,14 @@ if [ ! -d /Applications ]; then make -f otherbuilds/Makefile.linux g++ main/main.cpp lib/librubberband.a -I. -Isrc -o test -lsndfile -lsamplerate -lpthread ./test -V + g++ main/main.cpp single/RubberBandSingle.cpp -o test_single -lsndfile + ./test_single -V else make -f otherbuilds/Makefile.macos c++ main/main.cpp lib/librubberband.a -I. -Isrc -o test -lsndfile -lsamplerate -framework Accelerate ./test -V make -f otherbuilds/Makefile.macos clean make -f otherbuilds/Makefile.ios + c++ main/main.cpp single/RubberBandSingle.cpp -o test_single -lsndfile + ./test_single -V fi From 1f8173b6198ecd6ca60e8d40ca319aad5b5cf135 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 4 Oct 2021 12:29:48 +0100 Subject: [PATCH 24/32] Further relative paths --- src/StretcherImpl.h | 2 +- src/rubberband-c.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/StretcherImpl.h b/src/StretcherImpl.h index 1f15bd2..3844720 100644 --- a/src/StretcherImpl.h +++ b/src/StretcherImpl.h @@ -24,7 +24,7 @@ #ifndef RUBBERBAND_STRETCHERIMPL_H #define RUBBERBAND_STRETCHERIMPL_H -#include "rubberband/RubberBandStretcher.h" +#include "../rubberband/RubberBandStretcher.h" #include "dsp/Window.h" #include "dsp/SincWindow.h" diff --git a/src/rubberband-c.cpp b/src/rubberband-c.cpp index 03dfa6b..4230581 100644 --- a/src/rubberband-c.cpp +++ b/src/rubberband-c.cpp @@ -21,8 +21,8 @@ you must obtain a valid commercial licence before doing so. */ -#include "rubberband/rubberband-c.h" -#include "rubberband/RubberBandStretcher.h" +#include "../rubberband/rubberband-c.h" +#include "../rubberband/RubberBandStretcher.h" struct RubberBandState_ { From 30a114b0833bbc2fc9f7919557a3c867a5445b74 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 4 Oct 2021 13:10:59 +0100 Subject: [PATCH 25/32] Add framework, reorder a little --- otherbuilds/check.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/otherbuilds/check.sh b/otherbuilds/check.sh index d349837..874f6df 100755 --- a/otherbuilds/check.sh +++ b/otherbuilds/check.sh @@ -10,8 +10,8 @@ else make -f otherbuilds/Makefile.macos c++ main/main.cpp lib/librubberband.a -I. -Isrc -o test -lsndfile -lsamplerate -framework Accelerate ./test -V + c++ main/main.cpp single/RubberBandSingle.cpp -o test_single -lsndfile -framework Accelerate + ./test_single -V make -f otherbuilds/Makefile.macos clean make -f otherbuilds/Makefile.ios - c++ main/main.cpp single/RubberBandSingle.cpp -o test_single -lsndfile - ./test_single -V fi From d650006400509e7baa31dd351b965bc48cecf311 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Mon, 4 Oct 2021 13:46:52 +0100 Subject: [PATCH 26/32] Minor wording tweak --- single/RubberBandSingle.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/single/RubberBandSingle.cpp b/single/RubberBandSingle.cpp index 3885962..c123206 100644 --- a/single/RubberBandSingle.cpp +++ b/single/RubberBandSingle.cpp @@ -27,19 +27,21 @@ This is a single-file compilation unit for Rubber Band Library. To use the library in your project without building it separately, - include one of the headers rubberband/RubberBandStretcher.h if - your project is C++ or rubberband/rubberband-c.h if you need the C - ABI, then add this single C++ source file to your build. + include in your code either rubberband/RubberBandStretcher.h for + use in C++ or rubberband/rubberband-c.h if you need the C + interface, then add this single C++ source file to your build. Don't move this file into your source tree - keep it in the same place relative to the other Rubber Band code, so that the relative include paths work, and just add it to your list of build files. This produces a build using the built-in FFT and resampler - implementations, except on Apple platforms where the vDSP FFT is - used (and you will need the Accelerate framework when linking). If - you want any further customisation, consider using the full build - system and building a standalone library. + implementations, except on Apple platforms, where the vDSP FFT is + used (and where you will need the Accelerate framework when + linking). It should work correctly on any supported platform, but + may not be the fastest configuration available. For further + customisation, consider using the full build system to make a + standalone library. */ #define USE_BQRESAMPLER 1 From 18907f92f730744618ea1c4b8423a2239e2ddc3e Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 5 Oct 2021 09:41:37 +0100 Subject: [PATCH 27/32] This will be v2.0.0. We're going for the major version increment because the output in real-time mode intentionally changes, with what is intended to be tighter timing. The API and ABI are both unchanged from 1.9.2, but I think if the output is expected to change, that justifies the big bump --- Doxyfile | 2 +- meson.build | 4 ++-- rubberband/RubberBandStretcher.h | 2 +- rubberband/rubberband-c.h | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Doxyfile b/Doxyfile index d3f3ab3..674841f 100644 --- a/Doxyfile +++ b/Doxyfile @@ -31,7 +31,7 @@ PROJECT_NAME = "Rubber Band Library" # This could be handy for archiving the generated documentation or # if some version control system is used. -PROJECT_NUMBER = 1.9.2 +PROJECT_NUMBER = 2.0.0 # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. diff --git a/meson.build b/meson.build index 8e993dc..bf2f939 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,7 @@ project( 'Rubber Band Library', 'c', 'cpp', - version: '1.9.2', + version: '2.0.0', license: 'GPL-2.0-or-later', default_options: [ # All Rubber Band code is actually C++98, but some compilers no @@ -15,7 +15,7 @@ project( meson_version: '>= 0.53.0' ) -rubberband_dynamic_library_version = '2.1.4' +rubberband_dynamic_library_version = '2.1.5' system = host_machine.system() architecture = host_machine.cpu_family() diff --git a/rubberband/RubberBandStretcher.h b/rubberband/RubberBandStretcher.h index bb0362e..b2ee572 100644 --- a/rubberband/RubberBandStretcher.h +++ b/rubberband/RubberBandStretcher.h @@ -24,7 +24,7 @@ #ifndef RUBBERBAND_STRETCHER_H #define RUBBERBAND_STRETCHER_H -#define RUBBERBAND_VERSION "1.9.2" +#define RUBBERBAND_VERSION "2.0.0" #define RUBBERBAND_API_MAJOR_VERSION 2 #define RUBBERBAND_API_MINOR_VERSION 6 diff --git a/rubberband/rubberband-c.h b/rubberband/rubberband-c.h index 887ddf3..1f7451d 100644 --- a/rubberband/rubberband-c.h +++ b/rubberband/rubberband-c.h @@ -28,7 +28,7 @@ extern "C" { #endif -#define RUBBERBAND_VERSION "1.9.2" +#define RUBBERBAND_VERSION "2.0.0" #define RUBBERBAND_API_MAJOR_VERSION 2 #define RUBBERBAND_API_MINOR_VERSION 6 From 91c44fc8b8ea80f13bf07d1dfb4cbbb97e167166 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 5 Oct 2021 09:59:43 +0100 Subject: [PATCH 28/32] Make mention of built-in resampler; wording tweaks --- README.md | 73 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index c017f07..d635d1e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# Rubber Band +# Rubber Band Library An audio time-stretching and pitch-shifting library and utility program. @@ -29,12 +29,12 @@ License (GPL). You can redistribute it and/or modify it under the terms of the GPL; either version 2 of the License, or (at your option) any later version. See the file COPYING for more information. -If you wish to distribute code using the Rubber Band Library under -terms other than those of the GNU General Public License, you must -obtain a commercial licence from us before doing so. In particular, -you may not legally distribute through any Apple App Store unless you -have a commercial licence. See https://breakfastquay.com/rubberband/ -for licence terms. +If you wish to distribute code using Rubber Band Library under terms +other than those of the GNU General Public License, you must obtain a +commercial licence from us before doing so. In particular, you may not +legally distribute through any Apple App Store unless you have a +commercial licence. See https://breakfastquay.com/rubberband/ for +licence terms. If you have obtained a valid commercial licence, your licence supersedes this README and the enclosed COPYING file and you may @@ -68,7 +68,7 @@ our knowledge. See also the end of this README for detailed terms. 1. Code components 2. Using the Rubber Band command-line tool -3. Using the Rubber Band Library +3. Using Rubber Band Library 4. Compiling Rubber Band a. Building on Linux b. Building on macOS @@ -91,7 +91,7 @@ Rubber Band consists of: and FFT code; see section 3a below for details. * The Rubber Band command-line tool. This is in main/main.cpp. - This program uses the Rubber Band Library and also requires libsndfile + This program uses Rubber Band Library and also requires libsndfile (http://www.mega-nerd.com/libsndfile/, licensed under the GNU Lesser General Public License) for audio file loading. @@ -127,12 +127,12 @@ In particular, different types of music may benefit from different "crispness" options (-c flag with a numerical argument from 0 to 6). -## 3. Using the Rubber Band Library +## 3. Using Rubber Band Library -The Rubber Band Library has a public API that consists of one C++ -class, called RubberBandStretcher in the RubberBand namespace. You -should `#include ` to use this -class. There is extensive documentation in the class header. +Rubber Band has a public API that consists of one C++ class, called +`RubberBandStretcher` in the `RubberBand` namespace. You should +`#include ` to use this class. +There is extensive documentation in the class header. A header with C language bindings is also provided in ``. This is a wrapper around the C++ @@ -158,18 +158,26 @@ for modification and redistribution) unless you have separately acquired a commercial licence from the author. -## 4. Compiling the Rubber Band Library +## 4. Compiling Rubber Band Library -The primary supported build system for the Rubber Band Library on all -platforms is Meson (https://mesonbuild.com). The Meson build system -can be used to build all targets (static and dynamic library, -command-line utility, and plugins) and to cross-compile. +The primary supported build system for Rubber Band on all platforms is +Meson (https://mesonbuild.com). The Meson build system can be used to +build all targets (static and dynamic library, command-line utility, +and plugins) and to cross-compile. -If you only need a static library and don't wish to use Meson, some +☞ If you only need a static library and don't wish to use Meson, some alternative build files (Makefiles and Visual C++ projects) are included in the `otherbuilds` directory. See the platform-specific build sections below for more details. +☞ If you want to include Rubber Band in a C++ project and would prefer +not to compile a separate library, there is a single `.cpp` file at +`single/RubberBandSingle.cpp` which can be added to your project +as-is. It produces a single compilation-unit build with the built-in +FFT and resampler implementations with no further library +dependencies. See the comments at the top of that file for more +information. + To build with Meson, ensure Meson and Ninja are installed and run: ``` @@ -193,7 +201,7 @@ $ meson build -Dipp_path=/opt/intel/ipp The options are documented in the library- and platform-specific sections below. -The Rubber Band Library is written entirely in C++ to the C++98 +Rubber Band Library is written entirely in C++ to the C++98 standard. It is unlikely to make any difference (performance or otherwise) which C++ standard your compiler uses - as long as it's no older than C++98! @@ -268,8 +276,8 @@ See "FFT and resampler selection" below for further build options. Note that you cannot legally distribute applications using Rubber Band in the Mac App Store, unless you have first obtained a commercial -licence for the Rubber Band Library. GPL code is not permitted in the -app store. See https://breakfastquay.com/technology/license.html for +licence for Rubber Band Library. GPL code is not permitted in the app +store. See https://breakfastquay.com/technology/license.html for commercial terms. @@ -295,8 +303,8 @@ See "FFT and resampler selection" below for further build options. Note that you cannot legally distribute applications using Rubber Band in the iOS App Store, unless you have a first obtained a commercial -licence for the Rubber Band Library. GPL code is not permitted in the -app store. See https://breakfastquay.com/technology/license.html for +licence for Rubber Band Library. GPL code is not permitted in the app +store. See https://breakfastquay.com/technology/license.html for commercial terms. @@ -409,10 +417,21 @@ Library Build option CPP define Notes ---- ------------ ---------- ----- libsamplerate -DHAVE_LIBSAMPLERATE - -Dresampler=libsamplerate Best choice in most cases. + -Dresampler=libsamplerate Default when found. + Good choice in most cases. + +Built-in -Dfft=builtin -DUSE_BQRESAMPLER + Default when libsamplerate not found. + Can be distributed with either + the Rubber Band GPL or + commercial licence. Intended to + give best quality for time-varying + pitch shifts in real-time mode. + Newer than, and not as well-tested + as, libsamplerate. Speex -DUSE_SPEEX - -Dresampler=speex Bundled, can be distributed with + -Dresampler=speex Can be distributed with either the Rubber Band GPL or commercial licence. ``` From 9394a9ad09e07cf485b1fe80aa3a1dbf315bd843 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 5 Oct 2021 10:01:00 +0100 Subject: [PATCH 29/32] libresample no longer supported --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index d635d1e..71eafea 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,6 @@ our knowledge. See also the end of this README for detailed terms. * Intel IPP - Proprietary; licence needed for redistribution * KissFFT - BSD-like * libsamplerate - BSD-like from version 0.1.9 onwards - * libresample - LGPL * Speex - BSD-like * Pommier math functions - BSD-like From e868ff32de32ff17b546530a4e1fe24a64176ffb Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Tue, 12 Oct 2021 16:27:19 +0100 Subject: [PATCH 30/32] Remove some hopefully unnecessary references to global namespace --- otherbuilds/check.sh | 40 +++++++++++++++++++++++++++++++++++----- src/system/Allocators.h | 4 ++-- src/system/sysutils.h | 4 ++-- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/otherbuilds/check.sh b/otherbuilds/check.sh index 874f6df..0a3c3e5 100755 --- a/otherbuilds/check.sh +++ b/otherbuilds/check.sh @@ -1,17 +1,47 @@ #!/bin/bash set -eu + if [ ! -d /Applications ]; then + # Assumed to be Linux + + echo " *** Building static library using Linux-specific Makefile" +# make -f otherbuilds/Makefile.linux clean make -f otherbuilds/Makefile.linux + + echo " *** Linking against static library" g++ main/main.cpp lib/librubberband.a -I. -Isrc -o test -lsndfile -lsamplerate -lpthread + + echo " *** Running build from Linux-specific Makefile" ./test -V + + echo " *** Building with single-file source" g++ main/main.cpp single/RubberBandSingle.cpp -o test_single -lsndfile + + echo " *** Running build from single-file source" ./test_single -V + + echo " *** OK" + else - make -f otherbuilds/Makefile.macos - c++ main/main.cpp lib/librubberband.a -I. -Isrc -o test -lsndfile -lsamplerate -framework Accelerate - ./test -V - c++ main/main.cpp single/RubberBandSingle.cpp -o test_single -lsndfile -framework Accelerate - ./test_single -V + + echo " *** Building static library using macOS-specific Makefile" make -f otherbuilds/Makefile.macos clean + make -f otherbuilds/Makefile.macos + + echo " *** Linking against static library" + c++ main/main.cpp lib/librubberband.a -I. -Isrc -o test -lsndfile -lsamplerate -framework Accelerate + + echo " *** Running build from macOS-specific Makefile" + ./test -V + + echo " *** Building with single-file source" +c++ main/main.cpp single/RubberBandSingle.cpp -o test_single -lsndfile -framework Accelerate + + echo " *** Running build from single-file source" + ./test_single -V + + echo " *** Building static library using iOS-specific Makefile" + make -f otherbuilds/Makefile.ios clean make -f otherbuilds/Makefile.ios + fi diff --git a/src/system/Allocators.h b/src/system/Allocators.h index 1536954..6f1df54 100644 --- a/src/system/Allocators.h +++ b/src/system/Allocators.h @@ -343,12 +343,12 @@ public: abort(); #endif } - return ::RubberBand::allocate(n); + return RubberBand::allocate(n); } void deallocate(T *const p, const size_t) const { - ::RubberBand::deallocate(p); + RubberBand::deallocate(p); } template diff --git a/src/system/sysutils.h b/src/system/sysutils.h index 86a2896..40f24cc 100644 --- a/src/system/sysutils.h +++ b/src/system/sysutils.h @@ -135,8 +135,8 @@ extern void system_memorybarrier(); #include #include -#define MLOCK(a,b) ::mlock((char *)(a),(b)) -#define MUNLOCK(a,b) (::munlock((char *)(a),(b)) ? (::perror("munlock failed"), 0) : 0) +#define MLOCK(a,b) mlock((char *)(a),(b)) +#define MUNLOCK(a,b) (munlock((char *)(a),(b)) ? (perror("munlock failed"), 0) : 0) #define MUNLOCK_SAMPLEBLOCK(a) do { if (!(a).empty()) { const float &b = *(a).begin(); MUNLOCK(&b, (a).capacity() * sizeof(float)); } } while(0); #ifdef __APPLE__ From 674f44dcbac11866bccef20823ef46b644be3572 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 14 Oct 2021 10:03:37 +0100 Subject: [PATCH 31/32] Minor tidy --- README.md | 8 ++++---- main/main.cpp | 2 +- src/StretcherImpl.cpp | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 71eafea..bc6865e 100644 --- a/README.md +++ b/README.md @@ -200,10 +200,10 @@ $ 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++ to the C++98 -standard. It is unlikely to make any difference (performance or -otherwise) which C++ standard your compiler uses - as long as it's no -older than C++98! +Rubber Band Library is written entirely in C++ and requires a C++11 +compiler. It is unlikely to make any difference (performance or +otherwise) which C++ standard you compile with, as long as it's no +older than C++11. If you are building this software using either of the Speex or KissFFT library options, please be sure to review the terms for those diff --git a/main/main.cpp b/main/main.cpp index 72c47e0..8fbba44 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -648,7 +648,7 @@ int main(int argc, char **argv) int thisBlockSize = ibs; while (freqMapItr != freqMap.end()) { - size_t nextFreqFrame = freqMapItr->first; // + ts.getLatency(); + size_t nextFreqFrame = freqMapItr->first; if (nextFreqFrame <= countIn) { double s = frequencyshift * freqMapItr->second; if (debug > 0) { diff --git a/src/StretcherImpl.cpp b/src/StretcherImpl.cpp index 2d428bb..e3953d5 100644 --- a/src/StretcherImpl.cpp +++ b/src/StretcherImpl.cpp @@ -874,7 +874,6 @@ RubberBandStretcher::Impl::getLatency() const { if (!m_realtime) return 0; return lrint((m_aWindowSize/2) / m_pitchScale); -// return int(m_aWindowSize/2); } void From 05db791db3f8d27d7959957b407f228158cafc74 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 14 Oct 2021 13:28:29 +0100 Subject: [PATCH 32/32] Add pre-pad for realtime mode (equivalent to the padding RB does internally in offline mode already) --- main/main.cpp | 230 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 147 insertions(+), 83 deletions(-) diff --git a/main/main.cpp b/main/main.cpp index 8fbba44..789de9d 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -45,9 +45,6 @@ #include "../src/base/Profiler.h" -using namespace std; -using namespace RubberBand; - #ifdef _WIN32 using RubberBand::gettimeofday; #endif @@ -57,6 +54,11 @@ using RubberBand::usleep; #define strdup _strdup #endif +using RubberBand::RubberBandStretcher; + +using std::cerr; +using std::endl; + double tempo_convert(const char *str) { char *d = strchr((char *)str, ':'); @@ -488,9 +490,6 @@ int main(int argc, char **argv) << sf_strerror(sndfileOut) << endl; return 1; } - - int ibs = 1024; - size_t channels = sfinfo.channels; RubberBandStretcher::Options options = 0; if (realtime) options |= RubberBandStretcher::OptionProcessRealTime; @@ -568,6 +567,17 @@ int main(int argc, char **argv) float gain = 1.f; bool successful = false; + + const size_t channels = sfinfo.channels; + const int bs = 1024; + + float **cbuf = new float *[channels]; + for (size_t c = 0; c < channels; ++c) { + cbuf[c] = new float[bs]; + } + float *ibuf = new float[channels * bs]; + + int thisBlockSize; while (!successful) { // we may have to repeat with a modified // gain, if clipping occurs @@ -577,12 +587,6 @@ int main(int argc, char **argv) ratio, frequencyshift); ts.setExpectedInputDuration(sfinfo.frames); - float *fbuf = new float[channels * ibs]; - float **ibuf = new float *[channels]; - for (size_t i = 0; i < channels; ++i) { - ibuf[i] = new float[ibs]; - } - int frame = 0; int percent = 0; @@ -597,19 +601,17 @@ int main(int argc, char **argv) while (frame < sfinfo.frames) { int count = -1; - - if ((count = sf_readf_float(sndfile, fbuf, ibs)) <= 0) break; + if ((count = sf_readf_float(sndfile, ibuf, bs)) <= 0) break; for (size_t c = 0; c < channels; ++c) { for (int i = 0; i < count; ++i) { - float value = fbuf[i * channels + c]; - ibuf[c][i] = value; + cbuf[c][i] = ibuf[i * channels + c]; } } - bool final = (frame + ibs >= sfinfo.frames); + bool final = (frame + bs >= sfinfo.frames); - ts.study(ibuf, count, final); + ts.study(cbuf, count, final); int p = int((double(frame) * 100.0) / sfinfo.frames); if (p > percent || frame == 0) { @@ -619,7 +621,7 @@ int main(int argc, char **argv) } } - frame += ibs; + frame += bs; } if (!quiet) { @@ -641,11 +643,36 @@ int main(int argc, char **argv) countIn = 0; countOut = 0; bool clipping = false; + + // The stretcher only pads the start in offline mode; to avoid + // a fade in at the start, we pad it manually in RT mode + int toDrop = 0; + if (realtime) { + toDrop = int(ts.getLatency()); + int toPad = int(round(toDrop * frequencyshift)); + if (debug > 0) { + cerr << "padding start with " << toPad + << " samples in RT mode, will drop " << toDrop + << " at output" << endl; + } + if (toPad > 0) { + for (size_t c = 0; c < channels; ++c) { + for (int i = 0; i < bs; ++i) { + cbuf[c][i] = 0.f; + } + } + while (toPad > 0) { + int p = toPad; + if (p > bs) p = bs; + ts.process(cbuf, p, false); + toPad -= p; + } + } + } while (frame < sfinfo.frames) { - int count = -1; - int thisBlockSize = ibs; + thisBlockSize = bs; while (freqMapItr != freqMap.end()) { size_t nextFreqFrame = freqMapItr->first; @@ -666,8 +693,9 @@ int main(int argc, char **argv) break; } } - - if ((count = sf_readf_float(sndfile, fbuf, thisBlockSize)) < 0) { + + int count = -1; + if ((count = sf_readf_float(sndfile, ibuf, thisBlockSize)) < 0) { break; } @@ -675,51 +703,84 @@ int main(int argc, char **argv) for (size_t c = 0; c < channels; ++c) { for (int i = 0; i < count; ++i) { - float value = fbuf[i * channels + c]; - ibuf[c][i] = value; + cbuf[c][i] = ibuf[i * channels + c]; } } bool final = (frame + thisBlockSize >= sfinfo.frames); if (debug > 2) { - cerr << "count = " << count << ", ibs = " << thisBlockSize << ", frame = " << frame << ", frames = " << sfinfo.frames << ", final = " << final << endl; + cerr << "count = " << count << ", bs = " << thisBlockSize << ", frame = " << frame << ", frames = " << sfinfo.frames << ", final = " << final << endl; } - ts.process(ibuf, count, final); + ts.process(cbuf, count, final); - int avail = ts.available(); - if (debug > 1) cerr << "available = " << avail << endl; - - if (avail > 0) { - float **obf = new float *[channels]; - for (size_t i = 0; i < channels; ++i) { - obf[i] = new float[avail]; + int avail; + while ((avail = ts.available()) > 0) { + if (debug > 1) { + cerr << "available = " << avail << endl; } - ts.retrieve(obf, avail); - countOut += avail; - float *fobf = new float[channels * avail]; + + thisBlockSize = avail; + if (thisBlockSize > bs) { + thisBlockSize = bs; + } + + if (toDrop > 0) { + int dropHere = toDrop; + if (dropHere > thisBlockSize) { + dropHere = thisBlockSize; + } + if (debug > 1) { + cerr << "toDrop = " << toDrop << ", dropping " + << dropHere << " of " << avail << endl; + } + ts.retrieve(cbuf, dropHere); + toDrop -= dropHere; + avail -= dropHere; + continue; + } + + if (debug > 2) { + cerr << "retrieving block of " << thisBlockSize << endl; + } + ts.retrieve(cbuf, thisBlockSize); + + if (realtime && final) { + // (in offline mode the stretcher handles this itself) + size_t ideal = size_t(countIn * ratio); + if (debug > 2) { + cerr << "at end, ideal = " << ideal + << ", countOut = " << countOut + << ", thisBlockSize = " << thisBlockSize << endl; + } + if (countOut + thisBlockSize > ideal) { + thisBlockSize = ideal - countOut; + if (debug > 1) { + cerr << "truncated final block to " << thisBlockSize + << endl; + } + } + } + + countOut += thisBlockSize; + for (size_t c = 0; c < channels; ++c) { - for (int i = 0; i < avail; ++i) { - float value = gain * obf[c][i]; + for (int i = 0; i < thisBlockSize; ++i) { + float value = gain * cbuf[c][i]; if (ignoreClipping) { // i.e. just clamp, don't bail out if (value > 1.f) value = 1.f; if (value < -1.f) value = -1.f; } else { if (value >= 1.f || value < -1.f) { clipping = true; - gain = (0.999f / fabsf(obf[c][i])); + gain = (0.999f / fabsf(cbuf[c][i])); } } - fobf[i * channels + c] = value; + ibuf[i * channels + c] = value; } } - sf_writef_float(sndfileOut, fobf, avail); - delete[] fobf; - for (size_t i = 0; i < channels; ++i) { - delete[] obf[i]; - } - delete[] obf; + sf_writef_float(sndfileOut, ibuf, thisBlockSize); } if (clipping) { @@ -753,7 +814,7 @@ int main(int argc, char **argv) } } - frame += thisBlockSize; + frame += count; } if (!successful) { @@ -765,48 +826,51 @@ int main(int argc, char **argv) if (!quiet) { cerr << "\r " << endl; } + int avail; - while ((avail = ts.available()) >= 0) { - if (debug > 1) { cerr << "(completing) available = " << avail << endl; } - - if (avail > 0) { - float **obf = new float *[channels]; - for (size_t i = 0; i < channels; ++i) { - obf[i] = new float[avail]; - } - ts.retrieve(obf, avail); - countOut += avail; - float *fobf = new float[channels * avail]; - for (size_t c = 0; c < channels; ++c) { - for (int i = 0; i < avail; ++i) { - float value = gain * obf[c][i]; - if (value > 1.f) value = 1.f; - if (value < -1.f) value = -1.f; - fobf[i * channels + c] = value; - } - } - - sf_writef_float(sndfileOut, fobf, avail); - delete[] fobf; - for (size_t i = 0; i < channels; ++i) { - delete[] obf[i]; - } - delete[] obf; - } else { - usleep(10000); - } - } - - delete[] fbuf; - for (size_t i = 0; i < channels; ++i) delete[] ibuf[i]; - delete[] ibuf; + if (avail == 0) { + if (realtime || + (options & RubberBandStretcher::OptionThreadingNever)) { + break; + } else { + usleep(10000); + } + } + + thisBlockSize = avail; + if (thisBlockSize > bs) { + thisBlockSize = bs; + } + + ts.retrieve(cbuf, thisBlockSize); + + countOut += thisBlockSize; + + for (size_t c = 0; c < channels; ++c) { + for (int i = 0; i < thisBlockSize; ++i) { + float value = gain * cbuf[c][i]; + if (value > 1.f) value = 1.f; + if (value < -1.f) value = -1.f; + ibuf[i * channels + c] = value; + } + } + + sf_writef_float(sndfileOut, ibuf, thisBlockSize); + } } + delete[] ibuf; + + for (size_t c = 0; c < channels; ++c) { + delete[] cbuf[c]; + } + delete[] cbuf; + sf_close(sndfile); sf_close(sndfileOut); @@ -834,7 +898,7 @@ int main(int argc, char **argv) cerr << "elapsed time: " << sec << " sec, in frames/sec: " << countIn/sec << ", out frames/sec: " << countOut/sec << endl; } - Profiler::dump(); + RubberBand::Profiler::dump(); return 0; }