inline void init_external_plugins()

in pedalboard/ExternalPlugin.h [1424:2078]


inline void init_external_plugins(py::module &m) {
  py::enum_<ExternalPluginReloadType>(
      m, "ExternalPluginReloadType",
      "Indicates the behavior of an external plugin when reset() is called.")
      .value("Unknown", ExternalPluginReloadType::Unknown,
             "The behavior of the plugin is unknown. This will force a full "
             "reinstantiation of the plugin every time reset is called.")
      .value(
          "ClearsAudioOnReset", ExternalPluginReloadType::ClearsAudioOnReset,
          "This plugin clears its internal buffers correctly when reset() is "
          "called. The plugin will not be reinstantiated when reset is called.")
      .value("PersistsAudioOnReset",
             ExternalPluginReloadType::PersistsAudioOnReset,
             "This plugin does not clear its internal buffers as expected when "
             "reset() is called. This will force a full reinstantiation of the "
             "plugin every time reset is called.")
      .export_values();

  py::class_<juce::AudioProcessorParameter>(
      m, "_AudioProcessorParameter",
      "An abstract base class for parameter objects that can be added to an "
      "AudioProcessor.")
      .def("__repr__",
           [](juce::AudioProcessorParameter &parameter) {
             std::ostringstream ss;
             ss << "<pedalboard.AudioProcessorParameter";
             ss << " name=\"" << parameter.getName(512).toStdString() << "\"";
             if (!parameter.getLabel().isEmpty())
               ss << " label=\"" << parameter.getLabel().toStdString() << "\"";
             if (parameter.isBoolean())
               ss << " boolean";
             if (parameter.isDiscrete())
               ss << " discrete";
             ss << " raw_value=" << parameter.getValue();
             ss << ">";
             return ss.str();
           })
      .def_property(
          "raw_value", &juce::AudioProcessorParameter::getValue,
          &juce::AudioProcessorParameter::setValue,
          "The internal value of this parameter. Convention is that this "
          "parameter should be between 0 and 1.0. This may or may not "
          "correspond with the value shown to the user.")
      .def_property_readonly(
          "default_raw_value", &juce::AudioProcessorParameter::getDefaultValue,
          "The default internal value of this parameter. Convention is that "
          "this parameter should be between 0 and 1.0. This may or may not "
          "correspond with the value shown to the user.")
      .def(
          "get_name",
          [](juce::AudioProcessorParameter &param, int length) {
            return param.getName(length).toStdString();
          },
          py::arg("maximum_string_length"),
          "Returns the name to display for this parameter, which is made to "
          "fit within the given string length")
      .def_property_readonly(
          "name",
          [](juce::AudioProcessorParameter &param) {
            return param.getName(512).toStdString();
          },
          "Returns the name to display for this parameter at its longest.")
      .def_property_readonly(
          "label",
          [](juce::AudioProcessorParameter &param) {
            return param.getLabel().toStdString();
          },
          "Some parameters may be able to return a label string for their "
          "units. For example \"Hz\" or \"%\".")
      .def_property_readonly(
          "num_steps", &juce::AudioProcessorParameter::getNumSteps,
          "Returns the number of steps that this parameter's range should be "
          "quantised into. See also: is_discrete, is_boolean.")
      .def_property_readonly("is_discrete",
                             &juce::AudioProcessorParameter::isDiscrete,
                             "Returns whether the parameter uses discrete "
                             "values, based on the result of getNumSteps, or "
                             "allows the host to select values continuously.")
      .def_property_readonly(
          "is_boolean", &juce::AudioProcessorParameter::isBoolean,
          "Returns whether the parameter represents a boolean switch, "
          "typically with \"On\" and \"Off\" states.")
      .def(
          "get_text_for_raw_value",
          [](juce::AudioProcessorParameter &param, float value,
             int maximumStringLength) {
            return param.getText(value, maximumStringLength).toStdString();
          },
          py::arg("raw_value"), py::arg("maximum_string_length") = 512,
          "Returns a textual version of the supplied normalised parameter "
          "value.")
      .def(
          "get_raw_value_for_text",
          [](juce::AudioProcessorParameter &param, std::string &text) {
            return param.getValueForText(text);
          },
          py::arg("string_value"),
          "Returns the raw value of the supplied text. Plugins may handle "
          "errors however they see fit, but will likely not raise "
          "exceptions.")
      .def_property_readonly(
          "is_orientation_inverted",
          &juce::AudioProcessorParameter::isOrientationInverted,
          "If true, this parameter operates in the reverse direction. (Not "
          "all plugin formats will actually use this information).")
      .def_property_readonly("is_automatable",
                             &juce::AudioProcessorParameter::isAutomatable,
                             "Returns true if this parameter can be automated.")
      .def_property_readonly("is_automatable",
                             &juce::AudioProcessorParameter::isAutomatable,
                             "Returns true if this parameter can be "
                             "automated (i.e.: scheduled to "
                             "change over time, in real-time, in a DAW).")
      .def_property_readonly(
          "is_meta_parameter", &juce::AudioProcessorParameter::isMetaParameter,
          "A meta-parameter is a parameter that changes other parameters.")
      .def_property_readonly(
          "index", &juce::AudioProcessorParameter::getParameterIndex,
          "The index of this parameter in its plugin's parameter list.")
      .def_property_readonly(
          "string_value",
          [](juce::AudioProcessorParameter &param) {
            return param.getCurrentValueAsText().toStdString();
          },
          "Returns the current value of the parameter as a string.");

  py::object externalPlugin =
      py::class_<AbstractExternalPlugin, Plugin,
                 std::shared_ptr<AbstractExternalPlugin>>(
          m, "ExternalPlugin", py::dynamic_attr(),
          "A wrapper around a third-party effect plugin.\n\nDon't use this "
          "directly; use one of :class:`pedalboard.VST3Plugin` or "
          ":class:`pedalboard.AudioUnitPlugin` instead.")
          .def(py::init([]() {
            throw py::type_error(
                "ExternalPlugin is an abstract base class - don't instantiate "
                "this directly, use its subclasses instead.");
            return nullptr;
          }))
          .def(
              "process",
              [](std::shared_ptr<AbstractExternalPlugin> self,
                 py::object midiMessages, float duration, float sampleRate,
                 unsigned int numChannels, unsigned long bufferSize,
                 bool reset) -> py::array_t<float> {
                throw py::type_error("ExternalPlugin is an abstract base class "
                                     "- use its subclasses instead.");
                py::array_t<float> nothing;
                return nothing;
              },
              EXTERNAL_PLUGIN_PROCESS_DOCSTRING, py::arg("midi_messages"),
              py::arg("duration"), py::arg("sample_rate"),
              py::arg("num_channels") = 2,
              py::arg("buffer_size") = DEFAULT_BUFFER_SIZE,
              py::arg("reset") = true)
          .def(
              "process",
              [](std::shared_ptr<Plugin> self, const py::array inputArray,
                 double sampleRate, unsigned int bufferSize, bool reset) {
                return process(inputArray, sampleRate, {self}, bufferSize,
                               reset);
              },
              EXTERNAL_PLUGIN_PROCESS_DOCSTRING, py::arg("input_array"),
              py::arg("sample_rate"),
              py::arg("buffer_size") = DEFAULT_BUFFER_SIZE,
              py::arg("reset") = true)
          .def(
              "__call__",
              [](std::shared_ptr<Plugin> self, const py::array inputArray,
                 double sampleRate, unsigned int bufferSize, bool reset) {
                return process(inputArray, sampleRate, {self}, bufferSize,
                               reset);
              },
              "Run an audio or MIDI buffer through this plugin, returning "
              "audio. Alias for :py:meth:`process`.",
              py::arg("input_array"), py::arg("sample_rate"),
              py::arg("buffer_size") = DEFAULT_BUFFER_SIZE,
              py::arg("reset") = true)
          .def(
              "__call__",
              [](std::shared_ptr<AbstractExternalPlugin> self,
                 py::object midiMessages, float duration, float sampleRate,
                 unsigned int numChannels, unsigned long bufferSize,
                 bool reset) -> py::array_t<float> {
                throw py::type_error("ExternalPlugin is an abstract base class "
                                     "- use its subclasses instead.");
                py::array_t<float> nothing;
                return nothing;
              },
              "Run an audio or MIDI buffer through this plugin, returning "
              "audio. Alias for :py:meth:`process`.",
              py::arg("midi_messages"), py::arg("duration"),
              py::arg("sample_rate"), py::arg("num_channels") = 2,
              py::arg("buffer_size") = DEFAULT_BUFFER_SIZE,
              py::arg("reset") = true);

#if (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX)
  py::class_<ExternalPlugin<juce::PatchedVST3PluginFormat>,
             AbstractExternalPlugin,
             std::shared_ptr<ExternalPlugin<juce::PatchedVST3PluginFormat>>>(
      m, "VST3Plugin",
      R"(A wrapper around third-party, audio effect or instrument plugins in
`Steinberg GmbH's VST3® <https://en.wikipedia.org/wiki/Virtual_Studio_Technology>`_
format.

VST3® plugins are supported on macOS, Windows, and Linux. However, VST3® plugin
files are not cross-compatible with different operating systems; a platform-specific
build of each plugin is required to load that plugin on a given platform. (For
example: a Windows VST3 plugin bundle will not load on Linux or macOS.)

.. warning::
    Some VST3® plugins may throw errors, hang, generate incorrect output, or
    outright crash if called from background threads. If you find that a VST3®
    plugin is not working as expected, try calling it from the main thread
    instead and `open a GitHub Issue to track the incompatibility
    <https://github.com/spotify/pedalboard/issues/new>`_.


*Support for instrument plugins introduced in v0.7.4.*

*Support for running VST3® plugins on background threads introduced in v0.8.8.*
)")
      .def(
          py::init([](std::string &pathToPluginFile, py::object parameterValues,
                      std::optional<std::string> pluginName,
                      float initializationTimeout) {
            std::shared_ptr<ExternalPlugin<juce::PatchedVST3PluginFormat>>
                plugin = std::make_shared<
                    ExternalPlugin<juce::PatchedVST3PluginFormat>>(
                    pathToPluginFile, pluginName, initializationTimeout);
            py::cast(plugin).attr("__set_initial_parameter_values__")(
                parameterValues);
            return plugin;
          }),
          py::arg("path_to_plugin_file"),
          py::arg("parameter_values") = py::none(),
          py::arg("plugin_name") = py::none(),
          py::arg("initialization_timeout") =
              DEFAULT_INITIALIZATION_TIMEOUT_SECONDS)
      .def("__repr__",
           [](ExternalPlugin<juce::PatchedVST3PluginFormat> &plugin) {
             std::ostringstream ss;
             ss << "<pedalboard.VST3Plugin";
             ss << " \"" << plugin.getName() << "\"";
             ss << " at " << &plugin;
             ss << ">";
             return ss.str();
           })
      .def("load_preset",
           &ExternalPlugin<juce::PatchedVST3PluginFormat>::loadPresetFile,
           "Load a VST3 preset file in .vstpreset format.",
           py::arg("preset_file_path"))
      .def_property(
          "preset_data",
          [](const ExternalPlugin<juce::PatchedVST3PluginFormat> &plugin) {
            juce::MemoryBlock presetData;
            plugin.getPreset(presetData);
            return py::bytes((const char *)presetData.getData(),
                             presetData.getSize());
          },
          [](ExternalPlugin<juce::PatchedVST3PluginFormat> &plugin,
             const py::bytes &presetData) {
            py::buffer_info info(py::buffer(presetData).request());
            plugin.setPreset(info.ptr, static_cast<size_t>(info.size));
          },
          "Get or set the current plugin state as bytes in .vstpreset "
          "format.\n\n"
          ".. warning::\n    This property can be set to change the "
          "plugin's internal state, but providing invalid data may cause the "
          "plugin to crash, taking the entire Python process down with it.")
      .def_static(
          "get_plugin_names_for_file",
          [](std::string filename) {
            return getPluginNamesForFile<juce::PatchedVST3PluginFormat>(
                filename);
          },
          "Return a list of plugin names contained within a given VST3 "
          "plugin (i.e.: a \".vst3\"). If the provided file cannot be "
          "scanned, "
          "an ImportError will be raised.")
      .def_property_readonly_static(
          "installed_plugins",
          [](py::object /* cls */) { return findInstalledVSTPluginPaths(); },
          "Return a list of paths to VST3 plugins installed in the default "
          "location on this system. This list may not be exhaustive, and "
          "plugins in this list are not guaranteed to be compatible with "
          "Pedalboard.")
      .def_property_readonly(
          "name",
          [](ExternalPlugin<juce::PatchedVST3PluginFormat> &plugin) {
            return plugin.getName().toStdString();
          },
          "The name of this plugin.")
      .def_property(
          "raw_state",
          [](const ExternalPlugin<juce::PatchedVST3PluginFormat> &plugin) {
            juce::MemoryBlock state;
            plugin.getState(state);
            return py::bytes((const char *)state.getData(), state.getSize());
          },
          [](ExternalPlugin<juce::PatchedVST3PluginFormat> &plugin,
             const py::bytes &state) {
            py::buffer_info info(py::buffer(state).request());
            plugin.setState(info.ptr, static_cast<size_t>(info.size));
          },
          "A :py:class:`bytes` object representing the plugin's internal "
          "state.\n\n"
          "For the VST3 format, this is usually an XML-encoded string "
          "prefixed with an 8-byte header and suffixed with a single null "
          "byte.\n\n"
          ".. warning::\n    This property can be set to change the "
          "plugin's internal state, but providing invalid data may cause the "
          "plugin to crash, taking the entire Python process down with it.")
      .def_property_readonly(
          "descriptive_name",
          [](ExternalPlugin<juce::PatchedVST3PluginFormat> &plugin) {
            return plugin.foundPluginDescription.descriptiveName.toStdString();
          },
          "A more descriptive name for this plugin. This may be the same as "
          "the 'name' field, but some plugins may provide an alternative "
          "name.\n\n*Introduced in v0.9.4.*")
      .def_property_readonly(
          "category",
          [](ExternalPlugin<juce::PatchedVST3PluginFormat> &plugin) {
            return plugin.foundPluginDescription.category.toStdString();
          },
          "A category that this plugin falls into, such as \"Dynamics\", "
          "\"Reverbs\", etc.\n\n*Introduced in v0.9.4.*")
      .def_property_readonly(
          "manufacturer_name",
          [](ExternalPlugin<juce::PatchedVST3PluginFormat> &plugin) {
            return plugin.foundPluginDescription.manufacturerName.toStdString();
          },
          "The name of the manufacturer of this plugin, as reported by the "
          "plugin itself.\n\n*Introduced in v0.9.4.*")
      .def_property_readonly(
          "version",
          [](ExternalPlugin<juce::PatchedVST3PluginFormat> &plugin) {
            return plugin.foundPluginDescription.version.toStdString();
          },
          "The version string for this plugin, as reported by the plugin "
          "itself.\n\n*Introduced in v0.9.4.*")
      .def_property_readonly(
          "is_instrument",
          [](ExternalPlugin<juce::PatchedVST3PluginFormat> &plugin) {
            return plugin.foundPluginDescription.isInstrument;
          },
          "True iff this plugin identifies itself as an instrument (generator, "
          "synthesizer, etc) plugin.\n\n*Introduced in v0.9.4.*")
      .def_property_readonly(
          "has_shared_container",
          [](ExternalPlugin<juce::PatchedVST3PluginFormat> &plugin) {
            return plugin.foundPluginDescription.hasSharedContainer;
          },
          "True iff this plugin is part of a multi-plugin "
          "container.\n\n*Introduced in v0.9.4.*")
      .def_property_readonly(
          "identifier",
          [](ExternalPlugin<juce::PatchedVST3PluginFormat> &plugin) {
            return plugin.foundPluginDescription.createIdentifierString()
                .toStdString();
          },
          "A string that can be saved and used to uniquely identify this "
          "plugin (and version) again.\n\n*Introduced in v0.9.4.*")
      .def_property_readonly(
          "reported_latency_samples",
          [](ExternalPlugin<juce::PatchedVST3PluginFormat> &plugin) {
            return plugin.getLatencyHint();
          },
          "The number of samples of latency (delay) that this plugin reports "
          "to introduce into the audio signal due to internal buffering "
          "and processing. Pedalboard automatically compensates for this "
          "latency during processing, so this property is present for "
          "informational purposes. Note that not all plugins correctly report "
          "the latency that they introduce, so this value may be inaccurate "
          "(especially if the plugin reports 0).\n\n*Introduced in v0.9.12.*")
      .def_property_readonly(
          "_parameters",
          &ExternalPlugin<juce::PatchedVST3PluginFormat>::getParameters,
          py::return_value_policy::reference_internal)
      .def("_get_parameter",
           &ExternalPlugin<juce::PatchedVST3PluginFormat>::getParameter,
           py::return_value_policy::reference_internal)
      .def("show_editor",
           &ExternalPlugin<juce::PatchedVST3PluginFormat>::showEditor,
           SHOW_EDITOR_DOCSTRING, py::arg("close_event") = py::none())
      .def(
          "process",
          [](std::shared_ptr<Plugin> self, const py::array inputArray,
             double sampleRate, unsigned int bufferSize, bool reset) {
            return process(inputArray, sampleRate, {self}, bufferSize, reset);
          },
          EXTERNAL_PLUGIN_PROCESS_DOCSTRING, py::arg("input_array"),
          py::arg("sample_rate"), py::arg("buffer_size") = DEFAULT_BUFFER_SIZE,
          py::arg("reset") = true)
      .def(
          "__call__",
          [](std::shared_ptr<Plugin> self, const py::array inputArray,
             double sampleRate, unsigned int bufferSize, bool reset) {
            return process(inputArray, sampleRate, {self}, bufferSize, reset);
          },
          "Run an audio or MIDI buffer through this plugin, returning "
          "audio. Alias for :py:meth:`process`.",
          py::arg("input_array"), py::arg("sample_rate"),
          py::arg("buffer_size") = DEFAULT_BUFFER_SIZE, py::arg("reset") = true)
      .def("process",
           &ExternalPlugin<juce::PatchedVST3PluginFormat>::renderMIDIMessages,
           EXTERNAL_PLUGIN_PROCESS_DOCSTRING, py::arg("midi_messages"),
           py::arg("duration"), py::arg("sample_rate"),
           py::arg("num_channels") = 2,
           py::arg("buffer_size") = DEFAULT_BUFFER_SIZE,
           py::arg("reset") = true)
      .def("__call__",
           &ExternalPlugin<juce::PatchedVST3PluginFormat>::renderMIDIMessages,
           "Run an audio or MIDI buffer through this plugin, returning "
           "audio. Alias for :py:meth:`process`.",
           py::arg("midi_messages"), py::arg("duration"),
           py::arg("sample_rate"), py::arg("num_channels") = 2,
           py::arg("buffer_size") = DEFAULT_BUFFER_SIZE,
           py::arg("reset") = true)
      .def_readwrite(
          "_reload_type",
          &ExternalPlugin<juce::PatchedVST3PluginFormat>::reloadType,
          "The behavior that this plugin exhibits when .reset() is called. "
          "This is an internal attribute which gets set on plugin "
          "instantiation and should only be accessed for debugging and "
          "testing.");
#endif

#if JUCE_PLUGINHOST_AU && JUCE_MAC
  py::class_<ExternalPlugin<juce::AudioUnitPluginFormat>,
             AbstractExternalPlugin,
             std::shared_ptr<ExternalPlugin<juce::AudioUnitPluginFormat>>>(
      m, "AudioUnitPlugin", R"(
A wrapper around third-party, audio effect or instrument
plugins in `Apple's Audio Unit <https://en.wikipedia.org/wiki/Audio_Units>`_
format.

Audio Unit plugins are only supported on macOS. This class will be
unavailable on non-macOS platforms. Plugin files must be installed
in the appropriate system-wide path for them to be
loadable (usually ``/Library/Audio/Plug-Ins/Components/`` or
``~/Library/Audio/Plug-Ins/Components/``).

For a plugin wrapper that works on Windows and Linux as well,
see :class:`pedalboard.VST3Plugin`.)

.. warning::
    Some Audio Unit plugins may throw errors, hang, generate incorrect output, or
    outright crash if called from background threads. If you find that a Audio Unit
    plugin is not working as expected, try calling it from the main thread
    instead and `open a GitHub Issue to track the incompatibility
    <https://github.com/spotify/pedalboard/issues/new>`_.

*Support for instrument plugins introduced in v0.7.4.*

*Support for running Audio Unit plugins on background threads introduced in v0.8.8.*

*Support for loading AUv3 plugins (* ``.appex`` *bundles) introduced in v0.9.5.*
)")
      .def(
          py::init([](std::string &pathToPluginFile, py::object parameterValues,
                      std::optional<std::string> pluginName,
                      float initializationTimeout) {
            std::shared_ptr<ExternalPlugin<juce::AudioUnitPluginFormat>>
                plugin = std::make_shared<
                    ExternalPlugin<juce::AudioUnitPluginFormat>>(
                    pathToPluginFile, pluginName, initializationTimeout);
            py::cast(plugin).attr("__set_initial_parameter_values__")(
                parameterValues);
            return plugin;
          }),
          py::arg("path_to_plugin_file"),
          py::arg("parameter_values") = py::none(),
          py::arg("plugin_name") = py::none(),
          py::arg("initialization_timeout") =
              DEFAULT_INITIALIZATION_TIMEOUT_SECONDS)
      .def("__repr__",
           [](const ExternalPlugin<juce::AudioUnitPluginFormat> &plugin) {
             std::ostringstream ss;
             ss << "<pedalboard.AudioUnitPlugin";
             ss << " \"" << plugin.getName() << "\"";
             ss << " at " << &plugin;
             ss << ">";
             return ss.str();
           })
      .def_static(
          "get_plugin_names_for_file",
          [](std::string filename) {
            return getPluginNamesForFile<juce::AudioUnitPluginFormat>(filename);
          },
          py::arg("filename"),
          "Return a list of plugin names contained within a given Audio Unit "
          "bundle (i.e.: a ``.component`` or ``.appex`` file). If the provided "
          "file cannot be scanned, an ``ImportError`` will be raised.\n\nNote "
          "that most Audio Units have a single plugin inside, but this method "
          "can be useful to determine if multiple plugins are present in one "
          "bundle, and if so, what their names are.")
      .def_property_readonly_static(
          "installed_plugins",
          [](py::object /* cls */) {
            return AudioUnitPathFinder::findInstalledAudioUnitPaths();
          },
          "Return a list of paths to Audio Units installed in the default "
          "location on this system. This list may not be exhaustive, and "
          "plugins in this list are not guaranteed to be compatible with "
          "Pedalboard.")
      .def_property_readonly(
          "name",
          [](ExternalPlugin<juce::AudioUnitPluginFormat> &plugin) {
            return plugin.getName().toStdString();
          },
          "The name of this plugin, as reported by the plugin itself.")
      .def_property(
          "raw_state",
          [](const ExternalPlugin<juce::AudioUnitPluginFormat> &plugin) {
            juce::MemoryBlock state;
            plugin.getState(state);
            return py::bytes((const char *)state.getData(), state.getSize());
          },
          [](ExternalPlugin<juce::AudioUnitPluginFormat> &plugin,
             const py::bytes &state) {
            py::buffer_info info(py::buffer(state).request());
            plugin.setState(info.ptr, static_cast<size_t>(info.size));
          },
          "A :py:class:`bytes` object representing the plugin's internal "
          "state.\n\n"
          "For the Audio Unit format, this is usually a binary property list "
          "that can be decoded or encoded with the built-in :py:mod:`plistlib` "
          "package.\n\n"
          ".. warning::\n    This property can be set to change the "
          "plugin's internal state, but providing invalid data may cause the "
          "plugin to crash, taking the entire Python process down with it.")
      .def_property_readonly(
          "name",
          [](ExternalPlugin<juce::AudioUnitPluginFormat> &plugin) {
            return plugin.getName().toStdString();
          },
          "The name of this plugin.")
      .def_property_readonly(
          "descriptive_name",
          [](ExternalPlugin<juce::AudioUnitPluginFormat> &plugin) {
            return plugin.foundPluginDescription.descriptiveName.toStdString();
          },
          "A more descriptive name for this plugin. This may be the same as "
          "the 'name' field, but some plugins may provide an alternative "
          "name.\n\n*Introduced in v0.9.4.*")
      .def_property_readonly(
          "category",
          [](ExternalPlugin<juce::AudioUnitPluginFormat> &plugin) {
            return plugin.foundPluginDescription.category.toStdString();
          },
          "A category that this plugin falls into, such as \"Dynamics\", "
          "\"Reverbs\", etc.\n\n*Introduced in v0.9.4.*")
      .def_property_readonly(
          "manufacturer_name",
          [](ExternalPlugin<juce::AudioUnitPluginFormat> &plugin) {
            return plugin.foundPluginDescription.manufacturerName.toStdString();
          },
          "The name of the manufacturer of this plugin, as reported by the "
          "plugin itself.\n\n*Introduced in v0.9.4.*")
      .def_property_readonly(
          "version",
          [](ExternalPlugin<juce::AudioUnitPluginFormat> &plugin) {
            return plugin.foundPluginDescription.version.toStdString();
          },
          "The version string for this plugin, as reported by the plugin "
          "itself.\n\n*Introduced in v0.9.4.*")
      .def_property_readonly(
          "is_instrument",
          [](ExternalPlugin<juce::AudioUnitPluginFormat> &plugin) {
            return plugin.foundPluginDescription.isInstrument;
          },
          "True iff this plugin identifies itself as an instrument (generator, "
          "synthesizer, etc) plugin.\n\n*Introduced in v0.9.4.*")
      .def_property_readonly(
          "has_shared_container",
          [](ExternalPlugin<juce::AudioUnitPluginFormat> &plugin) {
            return plugin.foundPluginDescription.hasSharedContainer;
          },
          "True iff this plugin is part of a multi-plugin "
          "container.\n\n*Introduced in v0.9.4.*")
      .def_property_readonly(
          "identifier",
          [](ExternalPlugin<juce::AudioUnitPluginFormat> &plugin) {
            return plugin.foundPluginDescription.createIdentifierString()
                .toStdString();
          },
          "A string that can be saved and used to uniquely identify this "
          "plugin (and version) again.\n\n*Introduced in v0.9.4.*")
      .def_property_readonly(
          "reported_latency_samples",
          [](ExternalPlugin<juce::AudioUnitPluginFormat> &plugin) {
            return plugin.getLatencyHint();
          },
          "The number of samples of latency (delay) that this plugin reports "
          "to introduce into the audio signal due to internal buffering "
          "and processing. Pedalboard automatically compensates for this "
          "latency during processing, so this property is present for "
          "informational purposes. Note that not all plugins correctly report "
          "the latency that they introduce, so this value may be inaccurate "
          "(especially if the plugin reports 0).\n\n*Introduced in v0.9.12.*")
      .def_property_readonly(
          "_parameters",
          &ExternalPlugin<juce::AudioUnitPluginFormat>::getParameters,
          py::return_value_policy::reference_internal)
      .def("_get_parameter",
           &ExternalPlugin<juce::AudioUnitPluginFormat>::getParameter,
           py::return_value_policy::reference_internal)
      .def("show_editor",
           &ExternalPlugin<juce::AudioUnitPluginFormat>::showEditor,
           SHOW_EDITOR_DOCSTRING, py::arg("close_event") = py::none())
      .def(
          "process",
          [](std::shared_ptr<Plugin> self, const py::array inputArray,
             double sampleRate, unsigned int bufferSize, bool reset) {
            return process(inputArray, sampleRate, {self}, bufferSize, reset);
          },
          EXTERNAL_PLUGIN_PROCESS_DOCSTRING, py::arg("input_array"),
          py::arg("sample_rate"), py::arg("buffer_size") = DEFAULT_BUFFER_SIZE,
          py::arg("reset") = true)
      .def(
          "__call__",
          [](std::shared_ptr<Plugin> self, const py::array inputArray,
             double sampleRate, unsigned int bufferSize, bool reset) {
            return process(inputArray, sampleRate, {self}, bufferSize, reset);
          },
          "Run an audio or MIDI buffer through this plugin, returning "
          "audio. Alias for :py:meth:`process`.",
          py::arg("input_array"), py::arg("sample_rate"),
          py::arg("buffer_size") = DEFAULT_BUFFER_SIZE, py::arg("reset") = true)
      .def("process",
           &ExternalPlugin<juce::AudioUnitPluginFormat>::renderMIDIMessages,
           EXTERNAL_PLUGIN_PROCESS_DOCSTRING, py::arg("midi_messages"),
           py::arg("duration"), py::arg("sample_rate"),
           py::arg("num_channels") = 2,
           py::arg("buffer_size") = DEFAULT_BUFFER_SIZE,
           py::arg("reset") = true)
      .def("__call__",
           &ExternalPlugin<juce::AudioUnitPluginFormat>::renderMIDIMessages,
           "Run an audio or MIDI buffer through this plugin, returning "
           "audio. Alias for :py:meth:`process`.",
           py::arg("midi_messages"), py::arg("duration"),
           py::arg("sample_rate"), py::arg("num_channels") = 2,
           py::arg("buffer_size") = DEFAULT_BUFFER_SIZE,
           py::arg("reset") = true)
      .def_readwrite(
          "_reload_type",
          &ExternalPlugin<juce::AudioUnitPluginFormat>::reloadType,
          "The behavior that this plugin exhibits when .reset() is called. "
          "This is an internal attribute which gets set on plugin "
          "instantiation and should only be accessed for debugging and "
          "testing.");
#endif
}