Audacity 3.2.0
StaffPadTimeAndPitch.cpp
Go to the documentation of this file.
6#include <algorithm>
7#include <cassert>
8#include <cmath>
9#include <memory>
10
11namespace
12{
13// Let's use StaffPad's default value. (We have to reproduce it here as it has
14// to be specified in the `setup` call.)
15constexpr auto maxBlockSize = 1024;
16
18 float** offsetBuffer, float* const* buffer, size_t numChannels,
19 size_t offset)
20{
21 for (auto i = 0u; i < numChannels; ++i)
22 offsetBuffer[i] = buffer[i] + offset;
23}
24
25int GetFftSize(int sampleRate, bool formantPreservationOn)
26{
27 if (
28 const auto fftSize =
30 return *fftSize;
31
32 // 44.1kHz maps to 4096 samples (i.e., 93ms) without formant preservation,
33 // and 2048 with.
34 // We grow the FFT size proportionally with the sample rate to keep the
35 // window duration roughly constant, with quantization due to the
36 // power-of-two constraint.
37 // If needed some time in the future, we can decouple analysis window and
38 // FFT sizes by zero-padding, allowing for very fine-grained window duration
39 // without compromising performance.
40 return 1 << (formantPreservationOn ? 11 : 12) +
41 (int)std::round(std::log2(sampleRate / 44100.));
42}
43
44std::unique_ptr<staffpad::TimeAndPitch> CreateTimeAndPitch(
45 int sampleRate, size_t numChannels,
47{
48 const auto fftSize = GetFftSize(sampleRate, params.preserveFormants);
49 auto shiftTimbreCb = params.preserveFormants && params.pitchRatio != 1. ?
50 [&](
51 double factor, std::complex<float>* spectrum,
52 const float* magnitude) {
53 shifter.Process(magnitude, spectrum, factor);
54 } :
56 auto timeAndPitch = std::make_unique<staffpad::TimeAndPitch>(
57 fftSize,
59 true),
60 std::move(shiftTimbreCb));
61
62 timeAndPitch->setup(static_cast<int>(numChannels), maxBlockSize);
63 timeAndPitch->setTimeStretchAndPitchFactor(
64 params.timeRatio, params.pitchRatio);
65
66 return timeAndPitch;
67}
68
69std::unique_ptr<FormantShifterLoggerInterface>
71{
72 if (
73 const auto logSample =
75 return std::make_unique<FormantShifterLogger>(sampleRate, *logSample);
76 return std::make_unique<DummyFormantShifterLogger>();
77}
78} // namespace
79
81 int sampleRate, size_t numChannels, TimeAndPitchSource& audioSource,
82 const Parameters& parameters)
83 : mSampleRate(sampleRate)
84 , mParameters(parameters)
85 , mFormantShifterLogger(GetFormantShifterLogger(sampleRate))
86 , mFormantShifter(
89 .value_or(0.002),
90 *mFormantShifterLogger)
91 , mAudioSource(audioSource)
92 , mReadBuffer(maxBlockSize, numChannels)
93 , mNumChannels(numChannels)
94{
98 if (
100 // No need for sophisticated comparison for pitch ratio, as our UI doesn't
101 // allow changes smaller than a cent.
104}
105
106void StaffPadTimeAndPitch::GetSamples(float* const* output, size_t outputLen)
107{
108 if (!mTimeAndPitch)
109 // Pass-through
110 return mAudioSource.Pull(output, outputLen);
111
112 auto numOutputSamples = 0u;
113 while (numOutputSamples < outputLen)
114 {
115 if (IllState())
116 {
117 for (auto i = 0u; i < mNumChannels; ++i)
118 std::fill_n(
119 output[i] + numOutputSamples, outputLen - numOutputSamples, 0.f);
120 return;
121 }
122 auto numOutputSamplesAvailable =
123 mTimeAndPitch->getNumAvailableOutputSamples();
124 while (numOutputSamplesAvailable <= 0)
125 {
126 auto numRequired = mTimeAndPitch->getSamplesToNextHop();
127 while (numRequired > 0)
128 {
129 const auto numSamplesToFeed = std::min(numRequired, maxBlockSize);
130 mAudioSource.Pull(mReadBuffer.Get(), numSamplesToFeed);
131 mFormantShifterLogger->NewSamplesComing(numSamplesToFeed);
132 mTimeAndPitch->feedAudio(mReadBuffer.Get(), numSamplesToFeed);
133 numRequired -= numSamplesToFeed;
134 }
135 numOutputSamplesAvailable =
136 mTimeAndPitch->getNumAvailableOutputSamples();
137 }
138 while (numOutputSamples < outputLen && numOutputSamplesAvailable > 0)
139 {
140 const auto numSamplesToGet =
141 std::min({ maxBlockSize, numOutputSamplesAvailable,
142 static_cast<int>(outputLen - numOutputSamples) });
143 // More-than-stereo isn't supported
144 assert(mNumChannels <= 2);
145 float* buffer[2] {};
146 GetOffsetBuffer(buffer, output, mNumChannels, numOutputSamples);
147 mTimeAndPitch->retrieveAudio(buffer, numSamplesToGet);
148 numOutputSamplesAvailable -= numSamplesToGet;
149 numOutputSamples += numSamplesToGet;
150 }
151 }
152}
153
155{
156 mParameters.pitchRatio = std::pow(2., cents / 1200.);
157 // If pitch shifing was zero before, now it isn't. If it was non-zero before,
158 // we don't unset the stretcher even if the new pitch shift is zero, or
159 // someone playing around with the effect could hear glitches.
160 if (!mTimeAndPitch)
162 else
163 mTimeAndPitch->setTimeStretchAndPitchFactor(
165}
166
168{
169 mParameters.preserveFormants = preserve;
170 const auto fftSize = GetFftSize(mSampleRate, preserve);
171 preserve ? mFormantShifter.Reset(fftSize) : mFormantShifter.Reset();
172 // FFT size is a constant of the stretcher, so we need to reset it - if there
173 // is a stretcher.
174 if (mTimeAndPitch)
176}
177
179{
182 auto numOutputSamplesToDiscard =
183 mTimeAndPitch->getLatencySamplesForStretchRatio(
186 while (numOutputSamplesToDiscard > 0)
187 {
188 if (IllState())
189 return;
190 auto numRequired = mTimeAndPitch->getSamplesToNextHop();
191 while (numRequired > 0)
192 {
193 const auto numSamplesToFeed = std::min(maxBlockSize, numRequired);
194 mAudioSource.Pull(container.Get(), numSamplesToFeed);
195 mTimeAndPitch->feedAudio(container.Get(), numSamplesToFeed);
196 numRequired -= numSamplesToFeed;
197 }
198 const auto totalNumSamplesToRetrieve = std::min(
199 mTimeAndPitch->getNumAvailableOutputSamples(),
200 numOutputSamplesToDiscard);
201 auto totalNumRetrievedSamples = 0;
202 while (totalNumRetrievedSamples < totalNumSamplesToRetrieve)
203 {
204 const auto numSamplesToRetrieve = std::min(
205 maxBlockSize, totalNumSamplesToRetrieve - totalNumRetrievedSamples);
206 mTimeAndPitch->retrieveAudio(container.Get(), numSamplesToRetrieve);
207 totalNumRetrievedSamples += numSamplesToRetrieve;
208 }
209 numOutputSamplesToDiscard -= totalNumSamplesToRetrieve;
210 }
211}
212
214{
215 // It doesn't require samples, yet it doesn't have output samples available.
216 // Note that this must not be a permanent state, and may recover if the user
217 // changes the pitch shift.
218 // TODO: try to fix this in the stretcher implementation.
219 return mTimeAndPitch->getSamplesToNextHop() <= 0 &&
220 mTimeAndPitch->getNumAvailableOutputSamples() <= 0;
221}
int min(int a, int b)
EffectDistortionSettings params
void Reset(size_t fftSize)
void Process(const float *powerSpectrum, std::complex< float > *spectrum, double factor)
Processes spectrum in place, or does nothing if Reset(fftSize) wasn't called or Reset() was called si...
void OnFormantPreservationChange(bool preserve) override
FormantShifter mFormantShifter
StaffPadTimeAndPitch(int sampleRate, size_t numChannels, TimeAndPitchSource &, const Parameters &)
const std::unique_ptr< FormantShifterLoggerInterface > mFormantShifterLogger
TimeAndPitchSource & mAudioSource
void OnCentShiftChange(int cents) override
TimeAndPitchInterface::Parameters mParameters
void GetSamples(float *const *, size_t) override
std::unique_ptr< staffpad::TimeAndPitch > mTimeAndPitch
static bool IsPassThroughMode(double stretchRatio)
virtual void Pull(float *const *, size_t samplesPerChannel)=0
std::function< void(double factor, std::complex< float > *spectrum, const float *magnitude)> ShiftTimbreCb
Definition: TimeAndPitch.h:20
std::optional< int > GetLogSample(int sampleRate)
std::unique_ptr< staffpad::TimeAndPitch > CreateTimeAndPitch(int sampleRate, size_t numChannels, const TimeAndPitchInterface::Parameters &params, FormantShifter &shifter)
int GetFftSize(int sampleRate, bool formantPreservationOn)
void GetOffsetBuffer(float **offsetBuffer, float *const *buffer, size_t numChannels, size_t offset)
std::unique_ptr< FormantShifterLoggerInterface > GetFormantShifterLogger(int sampleRate)
fastfloat_really_inline void round(adjusted_mantissa &am, callback cb) noexcept
Definition: fast_float.h:2512
float *const * Get() const