mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
386 lines
16 KiB
C++
386 lines
16 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE library.
|
|
Copyright (c) 2022 - Raw Material Software Limited
|
|
|
|
JUCE is an open source library subject to commercial or open-source
|
|
licensing.
|
|
|
|
The code included in this file is provided under the terms of the ISC license
|
|
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
|
To use, copy, modify, and/or distribute this software for any purpose with or
|
|
without fee is hereby granted provided that the above copyright notice and
|
|
this permission notice appear in all copies.
|
|
|
|
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
|
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
|
DISCLAIMED.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
namespace juce
|
|
{
|
|
|
|
/**
|
|
An interpolator base class for resampling streams of floats.
|
|
|
|
Note that the resamplers are stateful, so when there's a break in the continuity
|
|
of the input stream you're feeding it, you should call reset() before feeding
|
|
it any new data. And like with any other stateful filter, if you're resampling
|
|
multiple channels, make sure each one uses its own interpolator object.
|
|
|
|
@see LagrangeInterpolator, CatmullRomInterpolator, WindowedSincInterpolator,
|
|
LinearInterpolator, ZeroOrderHoldInterpolator
|
|
|
|
@tags{Audio}
|
|
*/
|
|
template <class InterpolatorTraits, int memorySize>
|
|
class JUCE_API GenericInterpolator
|
|
{
|
|
static auto processReplacingCallback()
|
|
{
|
|
return [] (auto, auto newValue) { return newValue; };
|
|
}
|
|
|
|
static auto processAddingCallback (float gain)
|
|
{
|
|
return [gain] (auto oldValue, auto newValue) { return oldValue + gain * newValue; };
|
|
}
|
|
|
|
public:
|
|
GenericInterpolator() noexcept { reset(); }
|
|
|
|
GenericInterpolator (GenericInterpolator&&) noexcept = default;
|
|
GenericInterpolator& operator= (GenericInterpolator&&) noexcept = default;
|
|
|
|
/** Returns the latency of the interpolation algorithm in isolation.
|
|
|
|
In the context of resampling the total latency of a process using
|
|
the interpolator is the base latency divided by the speed ratio.
|
|
*/
|
|
static constexpr float getBaseLatency() noexcept
|
|
{
|
|
return InterpolatorTraits::algorithmicLatency;
|
|
}
|
|
|
|
/** Resets the state of the interpolator.
|
|
|
|
Call this when there's a break in the continuity of the input data stream.
|
|
*/
|
|
void reset() noexcept
|
|
{
|
|
indexBuffer = 0;
|
|
subSamplePos = 1.0;
|
|
std::fill (std::begin (lastInputSamples), std::end (lastInputSamples), 0.0f);
|
|
}
|
|
|
|
/** Resamples a stream of samples.
|
|
|
|
@param speedRatio the number of input samples to use for each output sample
|
|
@param inputSamples the source data to read from. This must contain at
|
|
least (speedRatio * numOutputSamplesToProduce) samples.
|
|
@param outputSamples the buffer to write the results into
|
|
@param numOutputSamplesToProduce the number of output samples that should be created
|
|
|
|
@returns the actual number of input samples that were used
|
|
*/
|
|
int process (double speedRatio,
|
|
const float* inputSamples,
|
|
float* outputSamples,
|
|
int numOutputSamplesToProduce) noexcept
|
|
{
|
|
return interpolateImpl (speedRatio,
|
|
inputSamples,
|
|
outputSamples,
|
|
numOutputSamplesToProduce,
|
|
processReplacingCallback());
|
|
}
|
|
|
|
/** Resamples a stream of samples.
|
|
|
|
@param speedRatio the number of input samples to use for each output sample
|
|
@param inputSamples the source data to read from. This must contain at
|
|
least (speedRatio * numOutputSamplesToProduce) samples.
|
|
@param outputSamples the buffer to write the results into
|
|
@param numOutputSamplesToProduce the number of output samples that should be created
|
|
@param numInputSamplesAvailable the number of available input samples. If it needs more samples
|
|
than available, it either wraps back for wrapAround samples, or
|
|
it feeds zeroes
|
|
@param wrapAround if the stream exceeds available samples, it wraps back for
|
|
wrapAround samples. If wrapAround is set to 0, it will feed zeroes.
|
|
|
|
@returns the actual number of input samples that were used
|
|
*/
|
|
int process (double speedRatio,
|
|
const float* inputSamples,
|
|
float* outputSamples,
|
|
int numOutputSamplesToProduce,
|
|
int numInputSamplesAvailable,
|
|
int wrapAround) noexcept
|
|
{
|
|
return interpolateImpl (speedRatio,
|
|
inputSamples,
|
|
outputSamples,
|
|
numOutputSamplesToProduce,
|
|
numInputSamplesAvailable,
|
|
wrapAround,
|
|
processReplacingCallback());
|
|
}
|
|
|
|
/** Resamples a stream of samples, adding the results to the output data
|
|
with a gain.
|
|
|
|
@param speedRatio the number of input samples to use for each output sample
|
|
@param inputSamples the source data to read from. This must contain at
|
|
least (speedRatio * numOutputSamplesToProduce) samples.
|
|
@param outputSamples the buffer to write the results to - the result values will be added
|
|
to any pre-existing data in this buffer after being multiplied by
|
|
the gain factor
|
|
@param numOutputSamplesToProduce the number of output samples that should be created
|
|
@param gain a gain factor to multiply the resulting samples by before
|
|
adding them to the destination buffer
|
|
|
|
@returns the actual number of input samples that were used
|
|
*/
|
|
int processAdding (double speedRatio,
|
|
const float* inputSamples,
|
|
float* outputSamples,
|
|
int numOutputSamplesToProduce,
|
|
float gain) noexcept
|
|
{
|
|
return interpolateImpl (speedRatio,
|
|
inputSamples,
|
|
outputSamples,
|
|
numOutputSamplesToProduce,
|
|
processAddingCallback (gain));
|
|
}
|
|
|
|
/** Resamples a stream of samples, adding the results to the output data
|
|
with a gain.
|
|
|
|
@param speedRatio the number of input samples to use for each output sample
|
|
@param inputSamples the source data to read from. This must contain at
|
|
least (speedRatio * numOutputSamplesToProduce) samples.
|
|
@param outputSamples the buffer to write the results to - the result values will be added
|
|
to any pre-existing data in this buffer after being multiplied by
|
|
the gain factor
|
|
@param numOutputSamplesToProduce the number of output samples that should be created
|
|
@param numInputSamplesAvailable the number of available input samples. If it needs more samples
|
|
than available, it either wraps back for wrapAround samples, or
|
|
it feeds zeroes
|
|
@param wrapAround if the stream exceeds available samples, it wraps back for
|
|
wrapAround samples. If wrapAround is set to 0, it will feed zeroes.
|
|
@param gain a gain factor to multiply the resulting samples by before
|
|
adding them to the destination buffer
|
|
|
|
@returns the actual number of input samples that were used
|
|
*/
|
|
int processAdding (double speedRatio,
|
|
const float* inputSamples,
|
|
float* outputSamples,
|
|
int numOutputSamplesToProduce,
|
|
int numInputSamplesAvailable,
|
|
int wrapAround,
|
|
float gain) noexcept
|
|
{
|
|
return interpolateImpl (speedRatio,
|
|
inputSamples,
|
|
outputSamples,
|
|
numOutputSamplesToProduce,
|
|
numInputSamplesAvailable,
|
|
wrapAround,
|
|
processAddingCallback (gain));
|
|
}
|
|
|
|
private:
|
|
//==============================================================================
|
|
forcedinline void pushInterpolationSample (float newValue) noexcept
|
|
{
|
|
lastInputSamples[indexBuffer] = newValue;
|
|
|
|
if (++indexBuffer == memorySize)
|
|
indexBuffer = 0;
|
|
}
|
|
|
|
forcedinline void pushInterpolationSamples (const float* input,
|
|
int numOutputSamplesToProduce) noexcept
|
|
{
|
|
if (numOutputSamplesToProduce >= memorySize)
|
|
{
|
|
const auto* const offsetInput = input + (numOutputSamplesToProduce - memorySize);
|
|
|
|
for (int i = 0; i < memorySize; ++i)
|
|
pushInterpolationSample (offsetInput[i]);
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < numOutputSamplesToProduce; ++i)
|
|
pushInterpolationSample (input[i]);
|
|
}
|
|
}
|
|
|
|
forcedinline void pushInterpolationSamples (const float* input,
|
|
int numOutputSamplesToProduce,
|
|
int numInputSamplesAvailable,
|
|
int wrapAround) noexcept
|
|
{
|
|
if (numOutputSamplesToProduce >= memorySize)
|
|
{
|
|
if (numInputSamplesAvailable >= memorySize)
|
|
{
|
|
pushInterpolationSamples (input,
|
|
numOutputSamplesToProduce);
|
|
}
|
|
else
|
|
{
|
|
pushInterpolationSamples (input + ((numOutputSamplesToProduce - numInputSamplesAvailable) - 1),
|
|
numInputSamplesAvailable);
|
|
|
|
if (wrapAround > 0)
|
|
{
|
|
numOutputSamplesToProduce -= wrapAround;
|
|
|
|
pushInterpolationSamples (input + ((numOutputSamplesToProduce - (memorySize - numInputSamplesAvailable)) - 1),
|
|
memorySize - numInputSamplesAvailable);
|
|
}
|
|
else
|
|
{
|
|
for (int i = numInputSamplesAvailable; i < memorySize; ++i)
|
|
pushInterpolationSample (0.0f);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (numOutputSamplesToProduce > numInputSamplesAvailable)
|
|
{
|
|
for (int i = 0; i < numInputSamplesAvailable; ++i)
|
|
pushInterpolationSample (input[i]);
|
|
|
|
const auto extraSamples = numOutputSamplesToProduce - numInputSamplesAvailable;
|
|
|
|
if (wrapAround > 0)
|
|
{
|
|
const auto* const offsetInput = input + (numInputSamplesAvailable - wrapAround);
|
|
|
|
for (int i = 0; i < extraSamples; ++i)
|
|
pushInterpolationSample (offsetInput[i]);
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < extraSamples; ++i)
|
|
pushInterpolationSample (0.0f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < numOutputSamplesToProduce; ++i)
|
|
pushInterpolationSample (input[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
template <typename Process>
|
|
int interpolateImpl (double speedRatio,
|
|
const float* input,
|
|
float* output,
|
|
int numOutputSamplesToProduce,
|
|
int numInputSamplesAvailable,
|
|
int wrap,
|
|
Process process)
|
|
{
|
|
auto originalIn = input;
|
|
bool exceeded = false;
|
|
|
|
const auto pushSample = [&]
|
|
{
|
|
if (exceeded)
|
|
{
|
|
pushInterpolationSample (0.0);
|
|
}
|
|
else
|
|
{
|
|
pushInterpolationSample (*input++);
|
|
|
|
if (--numInputSamplesAvailable <= 0)
|
|
{
|
|
if (wrap > 0)
|
|
{
|
|
input -= wrap;
|
|
numInputSamplesAvailable += wrap;
|
|
}
|
|
else
|
|
{
|
|
exceeded = true;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
interpolateImpl (speedRatio,
|
|
output,
|
|
numOutputSamplesToProduce,
|
|
process,
|
|
pushSample);
|
|
|
|
if (wrap == 0)
|
|
return (int) (input - originalIn);
|
|
|
|
return ((int) (input - originalIn) + wrap) % wrap;
|
|
}
|
|
|
|
template <typename Process>
|
|
int interpolateImpl (double speedRatio,
|
|
const float* input,
|
|
float* output,
|
|
int numOutputSamplesToProduce,
|
|
Process process)
|
|
{
|
|
int numUsed = 0;
|
|
|
|
interpolateImpl (speedRatio,
|
|
output,
|
|
numOutputSamplesToProduce,
|
|
process,
|
|
[this, input, &numUsed] { pushInterpolationSample (input[numUsed++]); });
|
|
|
|
return numUsed;
|
|
}
|
|
|
|
template <typename Process, typename PushSample>
|
|
void interpolateImpl (double speedRatio,
|
|
float* output,
|
|
int numOutputSamplesToProduce,
|
|
Process process,
|
|
PushSample pushSample)
|
|
{
|
|
auto pos = subSamplePos;
|
|
|
|
for (auto i = 0; i < numOutputSamplesToProduce; ++i)
|
|
{
|
|
while (pos >= 1.0)
|
|
{
|
|
pushSample();
|
|
pos -= 1.0;
|
|
}
|
|
|
|
*output = process (*output, InterpolatorTraits::valueAtOffset (lastInputSamples, (float) pos, indexBuffer));
|
|
++output;
|
|
pos += speedRatio;
|
|
}
|
|
|
|
subSamplePos = pos;
|
|
}
|
|
|
|
//==============================================================================
|
|
float lastInputSamples[(size_t) memorySize];
|
|
double subSamplePos = 1.0;
|
|
int indexBuffer = 0;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GenericInterpolator)
|
|
};
|
|
|
|
} // namespace juce
|