py::array_t renderMIDIMessages()

in pedalboard/ExternalPlugin.h [1227:1314]


  py::array_t<float> renderMIDIMessages(py::object midiMessages, float duration,
                                        float sampleRate,
                                        unsigned int numChannels,
                                        unsigned long bufferSize, bool reset) {

    // Tiny quality-of-life improvement to try to detect if people have swapped
    // the duration and sample_rate arguments:
    if ((duration == 48000 || duration == 44100 || duration == 22050 ||
         duration == 11025) &&
        sampleRate < 8000) {
      throw std::invalid_argument(
          "Plugin '" + pluginInstance->getName().toStdString() +
          "' was called with a duration argument of " +
          std::to_string(duration) + " and a sample_rate argument of " +
          std::to_string(sampleRate) +
          ". These arguments appear to be flipped, and may cause distorted "
          "audio to be rendered. Try reversing the order of the sample_rate "
          "and duration arguments provided to this method.");
    }

    std::scoped_lock<std::mutex>(this->mutex);

    py::array_t<float> outputArray;
    unsigned long outputSampleCount = duration * sampleRate;

    juce::MidiBuffer midiInputBuffer =
        parseMidiBufferFromPython(midiMessages, sampleRate);

    outputArray =
        py::array_t<float>({numChannels, (unsigned int)outputSampleCount});

    float *outputArrayPointer = static_cast<float *>(outputArray.request().ptr);

    py::gil_scoped_release release;

    if (pluginInstance) {
      if (reset)
        this->reset();

      juce::dsp::ProcessSpec spec;
      spec.sampleRate = sampleRate;
      spec.maximumBlockSize = (juce::uint32)bufferSize;
      spec.numChannels = (juce::uint32)numChannels;
      prepare(spec);

      if (!foundPluginDescription.isInstrument) {
        throw std::invalid_argument(
            "Plugin '" + pluginInstance->getName().toStdString() +
            "' expects audio as input, but was provided MIDI messages.");
      }

      if ((size_t)pluginInstance->getMainBusNumOutputChannels() !=
          numChannels) {
        throw std::invalid_argument(
            "Plugin '" + pluginInstance->getName().toStdString() +
            "' produces " +
            std::to_string(pluginInstance->getMainBusNumOutputChannels()) +
            "-channel output, but " + std::to_string(numChannels) +
            " channels of output were requested.");
      }

      std::memset((void *)outputArrayPointer, 0,
                  sizeof(float) * numChannels * outputSampleCount);

      for (unsigned long i = 0; i < outputSampleCount; i += bufferSize) {
        unsigned long chunkSampleCount =
            std::min((unsigned long)bufferSize, outputSampleCount - i);

        std::vector<float *> channelPointers(numChannels);
        for (size_t c = 0; c < numChannels; c++) {
          channelPointers[c] =
              (outputArrayPointer + (outputSampleCount * c) + i);
        }

        // Create an audio buffer that doesn't actually allocate anything, but
        // just points to the data in the output array.
        juce::AudioBuffer<float> audioChunk(
            channelPointers.data(), channelPointers.size(), chunkSampleCount);

        juce::MidiBuffer midiChunk;
        midiChunk.addEvents(midiInputBuffer, i, chunkSampleCount, -i);

        pluginInstance->processBlock(audioChunk, midiChunk);
      }
    }

    return outputArray;
  }