Skip to content

Commit

Permalink
Allow plugins to request custom extensions (#136)
Browse files Browse the repository at this point in the history
* Basic check that we can get the Reaper extension

* Muting a track works!

* Make extensionGet available in the plugin constructor

* Cleanup extension/capabilities interface

* Setting up dedicated example plugin for host-specific extensions

* Cleaning up function forward-declaration

* Workarounds in case user loads the plugin on REAPER's mater track
  • Loading branch information
jatinchowdhury18 authored Oct 17, 2023
1 parent 76a9c20 commit 5f369ad
Show file tree
Hide file tree
Showing 9 changed files with 291 additions and 2 deletions.
1 change: 1 addition & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ endif()

add_subdirectory(GainPlugin)
add_subdirectory(NoteNamesPlugin)
add_subdirectory(HostSpecificExtensionsPlugin)
61 changes: 61 additions & 0 deletions examples/HostSpecificExtensionsPlugin/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
if(CLAP_WRAP_PROJUCER_PLUGIN)
return()
endif()

if(NOT DEFINED REAPER_SDK_PATH)
message(STATUS "REAPER SDK path not supplied, skipping configuration for host-specific extensions plugin.")
return()
endif()

if (NOT EXISTS "${REAPER_SDK_PATH}/sdk/reaper_plugin.h")
message(WARNING "REAPER SDK: reaper_plugin.h not found! (Looking at: ${REAPER_SDK_PATH}/sdk/reaper_plugin.h)")
endif()

message(STATUS "Configuring host-specific extensions plugin with REAPER SDK: ${REAPER_SDK_PATH}")

juce_add_plugin(HostSpecificExtensionsPlugin
COMPANY_NAME "${COMPANY_NAME}"
PLUGIN_MANUFACTURER_CODE "${COMPANY_CODE}"
PLUGIN_CODE Hsep
FORMATS ${JUCE_FORMATS}
PRODUCT_NAME "Host-Specific Extensions Tester"
)

clap_juce_extensions_plugin(
TARGET HostSpecificExtensionsPlugin
CLAP_ID "org.free-audio.HostSpecificExtensionsPlugin"
CLAP_FEATURES audio-effect utility
CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES 64
)

target_sources(HostSpecificExtensionsPlugin PRIVATE
HostSpecificExtensionsPlugin.cpp
PluginEditor.cpp
)

target_compile_definitions(HostSpecificExtensionsPlugin PUBLIC
JUCE_DISPLAY_SPLASH_SCREEN=1
JUCE_REPORT_APP_USAGE=0
JUCE_WEB_BROWSER=0
JUCE_USE_CURL=0
JUCE_JACK=1
JUCE_ALSA=1
JUCE_MODAL_LOOPS_PERMITTED=1 # required for Linux FileChooser with JUCE 6.0.7
JUCE_VST3_CAN_REPLACE_VST2=0
)

target_include_directories(HostSpecificExtensionsPlugin
PRIVATE
"${REAPER_SDK_PATH}/sdk"
)

target_link_libraries(HostSpecificExtensionsPlugin
PRIVATE
juce::juce_audio_utils
juce::juce_audio_plugin_client
clap_juce_extensions
PUBLIC
juce::juce_recommended_config_flags
juce::juce_recommended_lto_flags
juce::juce_recommended_warning_flags
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#include "HostSpecificExtensionsPlugin.h"
#include "PluginEditor.h"

HostSpecificExtensionsPlugin::HostSpecificExtensionsPlugin()
: juce::AudioProcessor(BusesProperties()
.withInput("Input", juce::AudioChannelSet::stereo(), true)
.withOutput("Output", juce::AudioChannelSet::stereo(), true))
{
// load extensions here!
reaperPluginExtension =
static_cast<const reaper_plugin_info_t *>(getExtension("cockos.reaper_extension"));
jassert(reaperPluginExtension != nullptr || !juce::PluginHostType{}.isReaper());

if (reaperPluginExtension != nullptr)
{
// we want to check that we can load/use the extensions in the plugin constructor.
// for REAPER our silly test is to try muting track 0.
using GetMasterTrackFunc = MediaTrack *(*)(ReaProject *);
auto getMasterTrackFunc =
reinterpret_cast<GetMasterTrackFunc>(reaperPluginExtension->GetFunc("GetMasterTrack"));
auto *masterTrack = getMasterTrackFunc(nullptr);

using SetMuteFunc = int (*)(MediaTrack *track, int mute, int igngroupflags);
auto setMuteFunc =
reinterpret_cast<SetMuteFunc>(reaperPluginExtension->GetFunc("SetTrackUIMute"));
auto result = (*setMuteFunc)(masterTrack, 1, 0);
jassert(result == 1);
}
}

bool HostSpecificExtensionsPlugin::isBusesLayoutSupported(
const juce::AudioProcessor::BusesLayout &layouts) const
{
// only supports mono and stereo
if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono() &&
layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo())
return false;

// input and output layout must be the same
if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet())
return false;

return true;
}

void HostSpecificExtensionsPlugin::prepareToPlay(double, int) {}

void HostSpecificExtensionsPlugin::processBlock(juce::AudioBuffer<float> &, juce::MidiBuffer &) {}

juce::AudioProcessorEditor *HostSpecificExtensionsPlugin::createEditor()
{
return new PluginEditor(*this);
}

juce::String HostSpecificExtensionsPlugin::getPluginTypeString() const
{
if (wrapperType == juce::AudioProcessor::wrapperType_Undefined && is_clap)
return "CLAP";

return juce::AudioProcessor::getWrapperTypeDescription(wrapperType);
}

void HostSpecificExtensionsPlugin::getStateInformation(juce::MemoryBlock &) {}

void HostSpecificExtensionsPlugin::setStateInformation(const void *, int) {}

// This creates new instances of the plugin
juce::AudioProcessor *JUCE_CALLTYPE createPluginFilter()
{
return new HostSpecificExtensionsPlugin();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#pragma once

#include <juce_audio_utils/juce_audio_utils.h>
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE("-Wunused-parameter", "-Wextra-semi", "-Wnon-virtual-dtor")
#include <clap-juce-extensions/clap-juce-extensions.h>
JUCE_END_IGNORE_WARNINGS_GCC_LIKE

JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE("-Wc++98-compat-extra-semi",
"-Wgnu-anonymous-struct",
"-Wzero-as-null-pointer-constant",
"-Wextra-semi",
"-Wunused-parameter")
#include <reaper_plugin.h>
JUCE_END_IGNORE_WARNINGS_GCC_LIKE

class ModulatableFloatParameter;
class HostSpecificExtensionsPlugin : public juce::AudioProcessor,
public clap_juce_extensions::clap_juce_audio_processor_capabilities,
protected clap_juce_extensions::clap_properties
{
public:
HostSpecificExtensionsPlugin();

const juce::String getName() const override { return JucePlugin_Name; }
bool acceptsMidi() const override { return false; }
bool producesMidi() const override { return false; }
bool isMidiEffect() const override { return false; }

double getTailLengthSeconds() const override { return 0.0; }

int getNumPrograms() override { return 1; }
int getCurrentProgram() override { return 0; }
void setCurrentProgram(int) override {}
const juce::String getProgramName(int) override { return juce::String(); }
void changeProgramName(int, const juce::String &) override {}

bool isBusesLayoutSupported(const juce::AudioProcessor::BusesLayout &layouts) const override;
void prepareToPlay(double sampleRate, int samplesPerBlock) override;
void releaseResources() override {}
void processBlock(juce::AudioBuffer<float> &, juce::MidiBuffer &) override;
void processBlock(juce::AudioBuffer<double> &, juce::MidiBuffer &) override {}

bool hasEditor() const override { return true; }
juce::AudioProcessorEditor *createEditor() override;

void getStateInformation(juce::MemoryBlock &data) override;
void setStateInformation(const void *data, int sizeInBytes) override;

juce::String getPluginTypeString() const;

const reaper_plugin_info_t* reaperPluginExtension = nullptr;

private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(HostSpecificExtensionsPlugin)
};
54 changes: 54 additions & 0 deletions examples/HostSpecificExtensionsPlugin/PluginEditor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#include "PluginEditor.h"

PluginEditor::PluginEditor(HostSpecificExtensionsPlugin &plug)
: juce::AudioProcessorEditor(plug), plugin(plug)
{
addAndMakeVisible(changeTrackColour);
changeTrackColour.setEnabled(plugin.reaperPluginExtension != nullptr);
changeTrackColour.onClick = [reaperExt = plugin.reaperPluginExtension] {
using GetTrackFunc = MediaTrack *(*)(ReaProject *, int);
auto getTrackFunc = reinterpret_cast<GetTrackFunc>(reaperExt->GetFunc("GetTrack"));
auto *track0 = getTrackFunc(nullptr, 0);
if (track0 == nullptr)
return;

using ColorToNativeFunc = int (*)(int r, int g, int b);
auto colorToNativeFunc =
reinterpret_cast<ColorToNativeFunc>(reaperExt->GetFunc("ColorToNative"));

using SetTrackColorFunc = void (*)(MediaTrack *track, int color);
auto setTrackColorFunc =
reinterpret_cast<SetTrackColorFunc>(reaperExt->GetFunc("SetTrackColor"));

auto &rand = juce::Random::getSystemRandom();
const auto red = rand.nextInt(256);
const auto green = rand.nextInt(256);
const auto blue = rand.nextInt(256);
setTrackColorFunc(track0, colorToNativeFunc(red, green, blue));
};

setSize(300, 300);
}

void PluginEditor::resized()
{
changeTrackColour.setBounds(juce::Rectangle{100, 35}.withCentre(getLocalBounds().getCentre()));
}

void PluginEditor::paint(juce::Graphics &g)
{
g.fillAll(juce::Colours::grey);

auto bounds = getLocalBounds();

g.setColour(juce::Colours::black);
g.setFont(25.0f);
const auto titleText = "Host-Specific Extensions Plugin " + plugin.getPluginTypeString();
g.drawFittedText(titleText, bounds.removeFromTop(30), juce::Justification::centred, 1);

g.setFont(18.0f);
const auto reaperExtText =
"REAPER plugin extension: " +
juce::String(plugin.reaperPluginExtension != nullptr ? "FOUND" : "NOT FOUND");
g.drawFittedText(reaperExtText, bounds.removeFromTop(25), juce::Justification::centred, 1);
}
20 changes: 20 additions & 0 deletions examples/HostSpecificExtensionsPlugin/PluginEditor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#pragma once

#include "HostSpecificExtensionsPlugin.h"

class PluginEditor : public juce::AudioProcessorEditor
{
public:
explicit PluginEditor(HostSpecificExtensionsPlugin &plugin);
~PluginEditor() override = default;

void resized() override;
void paint(juce::Graphics &g) override;

private:
HostSpecificExtensionsPlugin &plugin;

juce::TextButton changeTrackColour { "Change Track Colour" };

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginEditor)
};
21 changes: 21 additions & 0 deletions include/clap-juce-extensions/clap-juce-extensions.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@
class ClapJuceWrapper;
struct JUCEParameterVariant;

namespace ClapAdapter
{
const clap_plugin *clap_create_plugin(const struct clap_plugin_factory *, const clap_host *,
const char *);
}


/** Forward declarations for any JUCE classes we might need. */
namespace juce
{
Expand Down Expand Up @@ -271,13 +278,27 @@ struct clap_juce_audio_processor_capabilities
return nullptr;
}

const void *getExtension(const char *name)
{
if (clapHostStatic != nullptr)
return clapHostStatic->get_extension(clapHostStatic, name);
if (extensionGet)
return extensionGet(name);
return nullptr;
}

private:
friend class ::ClapJuceWrapper;
std::function<void(const clap_event_param_value *)> parameterChangeHandler = nullptr;
std::function<JUCEParameterVariant *(clap_id)> lookupParamByID = nullptr;
std::function<void()> noteNamesChangedSignal = nullptr;
std::function<void()> remoteControlsChangedSignal = nullptr;
std::function<void(uint32_t)> suggestRemoteControlsPageSignal = nullptr;
std::function<const void *(const char *)> extensionGet = nullptr;

friend const clap_plugin *ClapAdapter::clap_create_plugin(const struct clap_plugin_factory *,
const clap_host *, const char *);
static const clap_host *clapHostStatic;
};

/*
Expand Down
1 change: 1 addition & 0 deletions src/extensions/clap-juce-extensions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ uint32_t clap_properties::clap_version_major{0}, clap_properties::clap_version_m

clap_properties::clap_properties() : is_clap{building_clap} {}

const clap_host* clap_juce_audio_processor_capabilities::clapHostStatic{nullptr};
} // namespace clap_juce_extensions
9 changes: 7 additions & 2 deletions src/wrapper/clap-juce-wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,9 @@ class ClapJuceWrapper : public clap::helpers::Plugin<
_host.remoteControlsSuggestPage(pageID);
});
};
processorAsClapExtensions->extensionGet = [this](const char *name) {
return _host.host()->get_extension(_host.host(), name);
};
}

const bool forceLegacyParamIDs = false;
Expand Down Expand Up @@ -2202,8 +2205,8 @@ static const clap_plugin_descriptor *clap_get_plugin_descriptor(const struct cla
return &ClapJuceWrapper::desc;
}

static const clap_plugin *clap_create_plugin(const struct clap_plugin_factory *,
const clap_host *host, const char *plugin_id)
const clap_plugin *clap_create_plugin(const struct clap_plugin_factory *, const clap_host *host,
const char *plugin_id)
{
juce::ScopedJuceInitialiser_GUI libraryInitialiser;

Expand All @@ -2218,8 +2221,10 @@ static const clap_plugin *clap_create_plugin(const struct clap_plugin_factory *,
clap_juce_extensions::clap_properties::clap_version_major = CLAP_VERSION_MAJOR;
clap_juce_extensions::clap_properties::clap_version_minor = CLAP_VERSION_MINOR;
clap_juce_extensions::clap_properties::clap_version_revision = CLAP_VERSION_REVISION;
clap_juce_extensions::clap_juce_audio_processor_capabilities::clapHostStatic = host;
auto *const pluginInstance = ::createPluginFilter();
clap_juce_extensions::clap_properties::building_clap = false;
clap_juce_extensions::clap_juce_audio_processor_capabilities::clapHostStatic = nullptr;
auto *wrapper = new ClapJuceWrapper(host, pluginInstance);
return wrapper->clapPlugin();
}
Expand Down

0 comments on commit 5f369ad

Please sign in to comment.