Skip to content

Commit

Permalink
Round FFT size to closest pow-of-two with 44.1kHz mapping exactly to …
Browse files Browse the repository at this point in the history
…4096.
  • Loading branch information
saintmatthieu committed Sep 27, 2023
1 parent 156e920 commit 0a852da
Show file tree
Hide file tree
Showing 7 changed files with 41 additions and 15 deletions.
3 changes: 2 additions & 1 deletion libraries/lib-stretching-sequence/ClipSegment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ ClipSegment::ClipSegment(
clip, durationToDiscard) }
, mSource { clip, durationToDiscard, direction }
, mStretcher { std::make_unique<StaffPadTimeAndPitch>(
clip.GetWidth(), mSource, GetStretchingParameters(clip)) }
clip.GetRate(), clip.GetWidth(), mSource,
GetStretchingParameters(clip)) }
{
}

Expand Down
16 changes: 16 additions & 0 deletions libraries/lib-time-and-pitch/StaffPad/TimeAndPitch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ inline float lagrange6(const float (&smp)[6], float t)
return (c0 + c1 * t) + (c2 + c3 * t) * t2 + (c4 + c5 * t) * t2 * t2;
}

inline int getFftSize(int sampleRate)
{
// 44.1kHz maps to 4096 samples (i.e., 93ms).
// We grow the FFT size proportionally with the sample rate to keep the
// window duration roughly constant, with quantization due to the
// power-of-two constraint.
// If needed some time in the future, we can decouple analysis window and
// FFT sizes by zero-padding, allowing for very fine-grained window duration
// without compromising performance.
return 1 << (12 + (int)std::round(std::log2(sampleRate / 44100.)));
}
} // namespace

struct TimeAndPitch::impl
Expand Down Expand Up @@ -76,6 +87,11 @@ struct TimeAndPitch::impl
std::vector<int> peak_index, trough_index;
};

TimeAndPitch::TimeAndPitch(int sampleRate)
: fftSize(getFftSize(sampleRate))
{
}

TimeAndPitch::~TimeAndPitch()
{
// Here for ~std::shared_ptr<impl>() to know ~impl()
Expand Down
4 changes: 2 additions & 2 deletions libraries/lib-time-and-pitch/StaffPad/TimeAndPitch.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace staffpad {
class TimeAndPitch
{
public:
TimeAndPitch() = default;
TimeAndPitch(int sampleRate);
~TimeAndPitch();

/**
Expand Down Expand Up @@ -76,7 +76,7 @@ class TimeAndPitch
void reset();

private:
static constexpr int fftSize = 4096;
const int fftSize;
static constexpr int overlap = 4;
static constexpr bool normalize_window = true; // compensate for ola window overlaps
static constexpr bool modulate_synthesis_hop = true;
Expand Down
10 changes: 6 additions & 4 deletions libraries/lib-time-and-pitch/StaffPadTimeAndPitch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ GetOffsetBuffer(float* const* buffer, size_t numChannels, size_t offset)
}

std::unique_ptr<staffpad::TimeAndPitch> MaybeCreateTimeAndPitch(
size_t numChannels, const TimeAndPitchInterface::Parameters& params)
int sampleRate, size_t numChannels,
const TimeAndPitchInterface::Parameters& params)
{
const auto timeRatio = params.timeRatio.value_or(1.);
const auto pitchRatio = params.pitchRatio.value_or(1.);
Expand All @@ -32,21 +33,22 @@ std::unique_ptr<staffpad::TimeAndPitch> MaybeCreateTimeAndPitch(
{
return nullptr;
}
auto timeAndPitch = std::make_unique<staffpad::TimeAndPitch>();
auto timeAndPitch = std::make_unique<staffpad::TimeAndPitch>(sampleRate);
timeAndPitch->setup(static_cast<int>(numChannels), maxBlockSize);
timeAndPitch->setTimeStretchAndPitchFactor(timeRatio, pitchRatio);
return timeAndPitch;
}
} // namespace

StaffPadTimeAndPitch::StaffPadTimeAndPitch(
size_t numChannels, TimeAndPitchSource& audioSource,
int sampleRate, size_t numChannels, TimeAndPitchSource& audioSource,
const Parameters& parameters)
: mAudioSource(audioSource)
, mReadBuffer(maxBlockSize, numChannels)
, mNumChannels(numChannels)
, mTimeRatio(parameters.timeRatio.value_or(1.))
, mTimeAndPitch(MaybeCreateTimeAndPitch(numChannels, parameters))
, mTimeAndPitch(
MaybeCreateTimeAndPitch(sampleRate, numChannels, parameters))
{
BootStretcher();
}
Expand Down
3 changes: 2 additions & 1 deletion libraries/lib-time-and-pitch/StaffPadTimeAndPitch.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ class TIME_AND_PITCH_API StaffPadTimeAndPitch final :
{
public:
StaffPadTimeAndPitch(
size_t numChannels, TimeAndPitchSource&, const Parameters&);
int sampleRate, size_t numChannels, TimeAndPitchSource&,
const Parameters&);
void GetSamples(float* const*, size_t) override;

private:
Expand Down
18 changes: 12 additions & 6 deletions libraries/lib-time-and-pitch/tests/StaffPadTimeAndPitchTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ TEST_CASE("StaffPadTimeAndPitch")
params.timeRatio = tr;
params.pitchRatio = pr;
TimeAndPitchRealSource src(input);
StaffPadTimeAndPitch sut(info.numChannels, src, std::move(params));
StaffPadTimeAndPitch sut(
info.sampleRate, info.numChannels, src, std::move(params));
constexpr size_t blockSize = 1234u;
auto offset = 0u;
while (offset < numOutputFrames)
Expand Down Expand Up @@ -104,7 +105,8 @@ TEST_CASE("StaffPadTimeAndPitch")
AudioContainer container(info.numFrames, info.numChannels);
TimeAndPitchInterface::Parameters params;
TimeAndPitchRealSource src(input);
StaffPadTimeAndPitch sut(info.numChannels, src, std::move(params));
StaffPadTimeAndPitch sut(
info.sampleRate, info.numChannels, src, std::move(params));
sut.GetSamples(container.Get(), info.numFrames);
// Calling REQUIRE(container.channelVectors == input) directly for large
// vectors might be rough on stdout in case of an error printout ...
Expand All @@ -114,16 +116,20 @@ TEST_CASE("StaffPadTimeAndPitch")

SECTION("Extreme stretch ratios")
{
constexpr auto originalDuration = 60.; // 1 minute
constexpr auto originalDuration = 60.; // 1 minute
constexpr auto targetDuration = 1. / 44100; // 1 sample
constexpr auto superSmallRatio = targetDuration / originalDuration;
constexpr auto requestedNumSamples = 1; // ... a single sample, but which is the condensed form of one minute of audio :D
constexpr auto requestedNumSamples =
1; // ... a single sample, but which is the condensed form of one
// minute of audio :D
constexpr auto numChannels = 1;
TimeAndPitchFakeSource src;
AudioContainer container(requestedNumSamples, numChannels);
TimeAndPitchInterface::Parameters params;
params.timeRatio = superSmallRatio;
StaffPadTimeAndPitch sut(numChannels, src, std::move(params));
sut.GetSamples(container.Get(), requestedNumSamples); // This is just not supposed to hang.
StaffPadTimeAndPitch sut(44100, numChannels, src, std::move(params));
sut.GetSamples(
container.Get(),
requestedNumSamples); // This is just not supposed to hang.
}
}
2 changes: 1 addition & 1 deletion libraries/lib-wave-track/WaveClip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1228,7 +1228,7 @@ void WaveClip::ApplyStretchRatio(const ProgressReporter& reportProgress)
PlaybackDirection::forward };
TimeAndPitchInterface::Parameters params;
params.timeRatio = stretchRatio;
StaffPadTimeAndPitch stretcher { numChannels, stretcherSource,
StaffPadTimeAndPitch stretcher { GetRate(), numChannels, stretcherSource,
std::move(params) };

// Post-rendering sample counts, i.e., stretched units
Expand Down

0 comments on commit 0a852da

Please sign in to comment.