in pedalboard/io/ResampledReadableAudioFile.h [175:331]
juce::AudioBuffer<float> readInternal(long long numSamples) {
// Note: We take a "write" lock here as calling readInternal will
// advance internal state:
ScopedTryWriteLock scopedTryWriteLock(objectLock);
if (!scopedTryWriteLock.isLocked()) {
throw std::runtime_error(
"Another thread is currently reading from this AudioFile. Note "
"that using multiple concurrent readers on the same AudioFile "
"object will produce nondeterministic results.");
}
long long samplesInResampledBuffer = 0;
juce::AudioBuffer<float> resampledBuffer(audioFile->getNumChannels(),
numSamples);
// Any samples in the existing outputBuffer from last time
// should be copied into resampledBuffer:
int samplesToPullFromOutputBuffer =
std::min(outputBuffer.getNumSamples(), (int)numSamples);
if (samplesToPullFromOutputBuffer > 0) {
for (int c = 0; c < resampledBuffer.getNumChannels(); c++) {
resampledBuffer.copyFrom(c, 0, outputBuffer, c, 0,
samplesToPullFromOutputBuffer);
}
samplesInResampledBuffer += samplesToPullFromOutputBuffer;
// Remove the used samples from outputBuffer:
if (outputBuffer.getNumSamples() - samplesToPullFromOutputBuffer) {
for (int c = 0; c < resampledBuffer.getNumChannels(); c++) {
// Use std::memmove instead of copyFrom here, as copyFrom is not
// overlap-safe.
std::memmove(
outputBuffer.getWritePointer(c),
outputBuffer.getReadPointer(c, samplesToPullFromOutputBuffer),
(outputBuffer.getNumSamples() - samplesToPullFromOutputBuffer) *
sizeof(float));
}
}
outputBuffer.setSize(outputBuffer.getNumChannels(),
outputBuffer.getNumSamples() -
samplesToPullFromOutputBuffer,
/* keepExistingContent */ true);
}
long long inputSamplesRequired =
(long long)(((numSamples - samplesToPullFromOutputBuffer) *
audioFile->getSampleRateAsDouble()) /
resampler.getTargetSampleRate());
// Make a juce::AudioBuffer that contains contiguous memory,
// which we can pass to readInternal:
std::vector<float> contiguousSourceSampleBuffer;
std::vector<float *> contiguousSourceSampleBufferPointers =
std::vector<float *>(audioFile->getNumChannels());
while (samplesInResampledBuffer < numSamples) {
// Cut or expand the contiguousSourceSampleBuffer to the required size:
contiguousSourceSampleBuffer.resize(
// Note: we need at least one element in this buffer or else we'll
// have no channel pointers to pass into the juce::AudioFile
// constructor!
std::max(1LL, audioFile->getNumChannels() * inputSamplesRequired));
std::fill_n(contiguousSourceSampleBuffer.begin(),
contiguousSourceSampleBuffer.size(), 0);
for (int c = 0; c < audioFile->getNumChannels(); c++) {
contiguousSourceSampleBufferPointers[c] =
contiguousSourceSampleBuffer.data() + (c * inputSamplesRequired);
}
juce::AudioBuffer<float> sourceSamples = juce::AudioBuffer<float>(
contiguousSourceSampleBufferPointers.data(),
audioFile->getNumChannels(), inputSamplesRequired);
std::optional<juce::AudioBuffer<float>> resamplerInput;
if (inputSamplesRequired > 0) {
// Read from the underlying audioFile into our contiguous buffer,
// which causes the sourceSamples AudioBuffer to be filled:
long long samplesRead = audioFile->readInternal(
audioFile->getNumChannels(), inputSamplesRequired,
contiguousSourceSampleBuffer.data());
// Resize the sourceSamples buffer to the number of samples read,
// without reallocating the memory underneath
// (still points at contiguousSourceSampleBuffer):
sourceSamples.setSize(audioFile->getNumChannels(), samplesRead,
/* keepExistingContent */ true,
/* clearExtraSpace */ false,
/* avoidReallocating */ true);
if (samplesRead < inputSamplesRequired) {
for (int c = 0; c < audioFile->getNumChannels(); c++) {
contiguousSourceSampleBufferPointers[c] =
contiguousSourceSampleBuffer.data() + (c * samplesRead);
}
sourceSamples = juce::AudioBuffer<float>(
contiguousSourceSampleBufferPointers.data(),
audioFile->getNumChannels(), samplesRead);
}
// If the underlying source ran out of samples, tell the resampler that
// we're done by feeding in an empty optional rather than an empty
// buffer:
if (sourceSamples.getNumSamples() == 0) {
resamplerInput = {};
} else {
resamplerInput = {sourceSamples};
}
} else {
resamplerInput = {sourceSamples};
}
// TODO: Provide an alternative interface to write to the output buffer
juce::AudioBuffer<float> newResampledSamples =
resampler.process(resamplerInput);
int offsetInNewResampledSamples = 0;
if (newResampledSamples.getNumSamples() >
numSamples - samplesInResampledBuffer) {
int samplesToCache = newResampledSamples.getNumSamples() -
(numSamples - samplesInResampledBuffer);
outputBuffer.setSize(newResampledSamples.getNumChannels(),
samplesToCache);
for (int c = 0; c < outputBuffer.getNumChannels(); c++) {
outputBuffer.copyFrom(c, 0, newResampledSamples, c,
newResampledSamples.getNumSamples() -
samplesToCache,
samplesToCache);
}
}
if (!resamplerInput && newResampledSamples.getNumSamples() == 0) {
resampledBuffer.setSize(resampledBuffer.getNumChannels(),
samplesInResampledBuffer,
/* keepExistingContent */ true);
break;
}
int startSample = samplesInResampledBuffer;
int samplesToCopy = std::min(
newResampledSamples.getNumSamples() - offsetInNewResampledSamples,
(int)(numSamples - samplesInResampledBuffer));
if (samplesToCopy > 0) {
for (int c = 0; c < resampledBuffer.getNumChannels(); c++) {
resampledBuffer.copyFrom(c, startSample, newResampledSamples, c,
offsetInNewResampledSamples, samplesToCopy);
}
}
samplesInResampledBuffer += samplesToCopy;
// TODO: Tune this carefully to avoid unnecessary calls to read()
// Too large, and we buffer too much audio using too much memory
// Too short, and we slow down
inputSamplesRequired = 1;
}
positionInTargetSampleRate += resampledBuffer.getNumSamples();
return resampledBuffer;
}