inline int process()

in pedalboard/process.h [32:155]


inline int process(juce::AudioBuffer<float> &ioBuffer,
                   juce::dsp::ProcessSpec spec,
                   const std::vector<std::shared_ptr<Plugin>> &plugins,
                   bool isProbablyLastProcessCall) {
  int totalOutputLatencySamples = 0;
  int expectedOutputLatency = 0;

  for (auto plugin : plugins) {
    if (!plugin)
      continue;
    expectedOutputLatency += plugin->getLatencyHint();
  }

  int intendedOutputBufferSize = ioBuffer.getNumSamples();

  if (expectedOutputLatency > 0 && isProbablyLastProcessCall) {
    // This is a hint - it's possible that the plugin(s) latency values
    // will change and we'll have to reallocate again later on.
    ioBuffer.setSize(ioBuffer.getNumChannels(),
                     ioBuffer.getNumSamples() + expectedOutputLatency,
                     /* keepExistingContent= */ true,
                     /* clearExtraSpace= */ true);
  }

  // Actually run the plugins over the ioBuffer, in small chunks, to minimize
  // memory usage:
  int startOfOutputInBuffer = 0;
  int lastSampleInBuffer = 0;

  for (auto plugin : plugins) {
    if (!plugin)
      continue;

    int pluginSamplesReceived = 0;

    unsigned int blockSize = spec.maximumBlockSize;
    for (unsigned int blockStart = startOfOutputInBuffer;
         blockStart < (unsigned int)intendedOutputBufferSize;
         blockStart += blockSize) {
      unsigned int blockEnd =
          std::min(blockStart + spec.maximumBlockSize,
                   static_cast<unsigned int>(intendedOutputBufferSize));
      blockSize = blockEnd - blockStart;

      auto ioBlock = juce::dsp::AudioBlock<float>(
          ioBuffer.getArrayOfWritePointers(), ioBuffer.getNumChannels(),
          blockStart, blockSize);
      juce::dsp::ProcessContextReplacing<float> context(ioBlock);

      int outputSamples = plugin->process(context);
      if (outputSamples < 0) {
        throw std::runtime_error(
            "A plugin returned a negative number of output samples! "
            "This is an internal Pedalboard error and should be reported.");
      }
      pluginSamplesReceived += outputSamples;

      int missingSamples = blockSize - outputSamples;
      if (missingSamples < 0) {
        throw std::runtime_error(
            "A plugin returned more samples than were asked for! "
            "This is an internal Pedalboard error and should be reported.");
      }

      if (missingSamples > 0 && pluginSamplesReceived > 0) {
        // This can only happen if the plugin we're using is returning us more
        // than one chunk of audio that's not completely full, which can
        // happen sometimes. In this case, we would end up with gaps in the
        // audio output:
        //               empty  empty  full   part
        //              [______|______|AAAAAA|__BBBB]
        //   end of most recently rendered block-->-^
        // We need to consolidate those gaps by moving them forward in time.
        // To do so, we take the section from the earliest known output to the
        // start of this block, and right-align it to the left side of the
        // current block's content:
        //               empty  empty  part   full
        //              [______|______|__AAAA|AABBBB]
        //   end of most recently rendered block-->-^
        for (int c = 0; c < ioBuffer.getNumChannels(); c++) {
          // Only move the samples received before this latest block was
          // rendered, as audio is right-aligned within blocks by convention.
          int samplesToMove = pluginSamplesReceived - outputSamples;
          float *outputStart =
              ioBuffer.getWritePointer(c) + totalOutputLatencySamples;
          float *expectedOutputEnd =
              ioBuffer.getWritePointer(c) + blockEnd - outputSamples;
          float *expectedOutputStart = expectedOutputEnd - samplesToMove;

          std::memmove((char *)expectedOutputStart, (char *)outputStart,
                       sizeof(float) * samplesToMove);
        }
      }

      lastSampleInBuffer =
          std::max(lastSampleInBuffer, (int)(blockStart + outputSamples));
      startOfOutputInBuffer += missingSamples;
      totalOutputLatencySamples += missingSamples;

      if (missingSamples && isProbablyLastProcessCall) {
        // Resize the IO buffer to give us a bit more room
        // on the end, so we can continue to write delayed output.
        // Only do this if we think this is the last time process is called.
        intendedOutputBufferSize += missingSamples;

        // If we need to reallocate, then we reallocate.
        if (intendedOutputBufferSize > ioBuffer.getNumSamples()) {
          ioBuffer.setSize(ioBuffer.getNumChannels(), intendedOutputBufferSize,
                           /* keepExistingContent= */ true,
                           /* clearExtraSpace= */ true);
        }
      }
    }
  }

  // Trim the output buffer down to size; this operation should be
  // allocation-free.
  jassert(intendedOutputBufferSize <= ioBuffer.getNumSamples());
  ioBuffer.setSize(ioBuffer.getNumChannels(), intendedOutputBufferSize,
                   /* keepExistingContent= */ true,
                   /* clearExtraSpace= */ true,
                   /* avoidReallocating= */ true);
  return intendedOutputBufferSize - totalOutputLatencySamples;
}