juce::AudioBuffer readInternal()

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;
  }