Skip to content

Instantly share code, notes, and snippets.

@atsushieno
Last active July 3, 2022 18:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save atsushieno/d47a82739c64595da2f15cd8bc87673a to your computer and use it in GitHub Desktop.
Save atsushieno/d47a82739c64595da2f15cd8bc87673a to your computer and use it in GitHub Desktop.
#include "CLAPAudioPluginFormat.h"
namespace clap_juce_hosting {
// HostedAudioProcessorParameter::setValue() implementation
uint32_t clap_juce_host_in_events_set_value_size(const struct clap_input_events *list) {
return 1;
}
const clap_event_header_t* clap_juce_host_in_events_set_value_get(const struct clap_input_events *list, uint32_t index) {
auto vp = (CLAPHostedAudioProcessorParameter::HostedParameterNewValue*) list->ctx;
auto parameter = vp->parameter;
auto value = vp->new_value;
delete vp; // should be correct
parameter->set_value_event = clap_event_param_value {
clap_event_header_t{sizeof(clap_event_param_value) - sizeof(clap_event_header_t), 0, CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_PARAM_VALUE, 0},
parameter->info.id, nullptr, -1, -1, -1, -1, value};
return &parameter->set_value_event.header;
}
void CLAPHostedAudioProcessorParameter::setValue(float newValue) {
// If the processor is actively running, then we have to put the event to the waiting queue and process them at process().
// If not, then the event queue is never processed, so we have to use param_ext.flush().
// It is somewhat complicated because we have to implement and set up an extra clap_input_events, and enqueue the event for it.
if (!ctx->is_processing) {
auto vp = new CLAPHostedAudioProcessorParameter::HostedParameterNewValue{this, newValue, 0}; // it is deleted at get().
set_value_input_events = clap_input_events{vp,
clap_juce_host_in_events_set_value_size,
clap_juce_host_in_events_set_value_get};
ctx->params_ext->flush(ctx->plugin, &set_value_input_events, nullptr);
} else {
// FIXME: assign appropriate time
// FIXME: should we pass CLAP_EVENT_IS_LIVE? It's not obvious from juce::HostedAudioProcessorParameter class.
set_value_event = clap_event_param_value {
clap_event_header_t{sizeof(clap_event_param_value), 0, CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_PARAM_VALUE, 0},
info.id, info.cookie, -1, -1, -1, -1, newValue};
instance->ctx->editor_event_list.push(&set_value_event.header);
}
}
// log support
void clap_juce_host_ext_log(const clap_host_t *host, clap_log_severity severity, const char *msg) {
auto ctx = (CLAPFactoryContext*) host->host_data;
ctx->logger.addEntry(severity, msg);
}
clap_host_log_t host_ext_log{clap_juce_host_ext_log};
// audio ports support
bool clap_juce_host_ext_audio_ports_is_rescan_flag_supported(const clap_host_t *host, uint32_t flag) {
return false; // TODO: implement
}
void clap_juce_host_ext_audio_ports_rescan(const clap_host_t *host, uint32_t flags) {
// TODO: implement
}
clap_host_audio_ports_t host_ext_audio_ports{
clap_juce_host_ext_audio_ports_is_rescan_flag_supported,
clap_juce_host_ext_audio_ports_rescan};
// note ports support
uint32_t clap_juce_host_ext_note_ports_supported_dialects(const clap_host_t *host) {
return CLAP_NOTE_DIALECT_MIDI || CLAP_NOTE_DIALECT_CLAP;
}
void clap_juce_host_ext_note_ports_rescan(const clap_host_t *host, uint32_t flags) {
// TODO: implement
}
clap_host_note_ports_t host_ext_note_ports{
clap_juce_host_ext_note_ports_supported_dialects,
clap_juce_host_ext_note_ports_rescan};
// thread check support
bool clap_juce_host_ext_is_main_thread(const clap_host_t *host) {
return juce::MessageManager::getInstance()->isThisTheMessageThread();
}
bool clap_juce_host_ext_is_audio_thread(const clap_host_t *host) {
auto ctx = (CLAPFactoryContext*) host->host_data;
return ctx->audio_thread_id == juce::Thread::getCurrentThreadId();
}
clap_host_thread_check_t host_ext_thread_check{clap_juce_host_ext_is_main_thread, clap_juce_host_ext_is_audio_thread};
// "thread pool" support
bool clap_juce_host_ext_request_exec(const clap_host_t *host, uint32_t num_tasks) {
auto ctx = (CLAPFactoryContext*) host->host_data;
ctx->realtimeParallelFor(num_tasks);
}
clap_host_thread_pool_t host_ext_thread_pool{clap_juce_host_ext_request_exec};
// audio ports config support
void clap_juce_host_ext_audio_ports_config_rescan(const clap_host_t *host) {
// TODO: implement
}
clap_host_audio_ports_config_t host_ext_audio_ports_config{clap_juce_host_ext_audio_ports_config_rescan};
// Timer support
void CLAPHostMultiTimer::timerCallback(int timerID) {
if (ctx->plugin) {
auto pluginTimer = (clap_plugin_timer_support_t*) ctx->plugin->get_extension(ctx->plugin, CLAP_EXT_TIMER_SUPPORT);
if (pluginTimer)
pluginTimer->on_timer(ctx->plugin, timerID);
}
}
bool clap_juce_host_ext_register_timer(const clap_host_t *host, uint32_t period_ms, clap_id *timer_id) {
auto ctx = (CLAPFactoryContext*) host->host_data;
std::mutex mutex;
mutex.lock();
*timer_id = ctx->last_timer_id++;
ctx->timer.startTimer(*timer_id, period_ms);
mutex.unlock();
}
bool clap_juce_host_ext_unregister_timer(const clap_host_t *host, clap_id timer_id) {
auto ctx = (CLAPFactoryContext*) host->host_data;
ctx->timer.stopTimer(timer_id);
}
clap_host_timer_support_t host_ext_timer_support{clap_juce_host_ext_register_timer, clap_juce_host_ext_unregister_timer};
// FD support
bool clap_juce_host_ext_register_fd(const clap_host_t *host, int fd, clap_posix_fd_flags_t flags) {
return false; // TODO: implement
}
bool clap_juce_host_ext_modify_fd(const clap_host_t *host, int fd, clap_posix_fd_flags_t flags) {
return false; // TODO: implement
}
bool clap_juce_host_ext_unregister_fd(const clap_host_t *host, int fd) {
return false; // TODO: implement
}
clap_host_posix_fd_support_t host_ext_posix_fd_support {
clap_juce_host_ext_register_fd,
clap_juce_host_ext_modify_fd,
clap_juce_host_ext_unregister_fd};
// file reference support
void clap_juce_host_ext_file_reference_changed(const clap_host_t *host) {
// TODO: implement
}
void clap_juce_host_ext_file_reference_set_dirty(const clap_host_t *host, clap_id resource_id) {
// TODO: implement
}
clap_host_file_reference host_ext_file_reference {
clap_juce_host_ext_file_reference_changed,
clap_juce_host_ext_file_reference_set_dirty
};
// latency support
void clap_juce_host_ext_latency_changed(const clap_host_t *host) {
// is it correct interpretation?
auto ctx = (CLAPFactoryContext*) host->host_data;
ctx->instance->setLatencySamples(ctx->latency_ext->get(ctx->plugin));
}
clap_host_latency_t host_ext_latency { clap_juce_host_ext_latency_changed };
// gui support
void clap_juce_host_ext_gui_resize_hints_changed(const clap_host_t *host) {
auto ctx = (CLAPFactoryContext*) host->host_data;
auto editor = (CLAPHostAudioProcessorEditor*) ctx->instance->getActiveEditor();
editor->updateResizeHints();
}
bool clap_juce_host_ext_gui_request_resize(const clap_host_t *host, uint32_t width, uint32_t height) {
auto ctx = (CLAPFactoryContext*) host->host_data;
auto editor = ctx->instance->getActiveEditor();
editor->setSize(width, height);
return true;
}
bool clap_juce_host_ext_gui_request_show(const clap_host_t *host) {
auto ctx = (CLAPFactoryContext*) host->host_data;
auto editor = (CLAPHostAudioProcessorEditor*) ctx->instance->getActiveEditor();
return editor->onShowRequested();
}
bool clap_juce_host_ext_gui_request_hide(const clap_host_t *host) {
auto ctx = (CLAPFactoryContext*) host->host_data;
auto editor = (CLAPHostAudioProcessorEditor*) ctx->instance->getActiveEditor();
return editor->onHideRequested();
}
void clap_juce_host_ext_gui_closed(const clap_host_t *host, bool was_destroyed) {
auto ctx = (CLAPFactoryContext*) host->host_data;
auto editor = ctx->instance->getActiveEditor();
// FIXME: implement the actual disposal?
}
clap_host_gui_t host_ext_gui {
clap_juce_host_ext_gui_resize_hints_changed,
clap_juce_host_ext_gui_request_resize,
clap_juce_host_ext_gui_request_show,
clap_juce_host_ext_gui_request_hide,
clap_juce_host_ext_gui_closed
};
void clap_juce_host_ext_params_rescan(const clap_host_t *host, clap_param_rescan_flags flags) {
auto ctx = (CLAPFactoryContext*) host->host_data;
ctx->instance->buildParameters();
}
void clap_juce_host_ext_params_clear(const clap_host_t *host, clap_id param_id, clap_param_clear_flags flags) {
auto ctx = (CLAPFactoryContext*) host->host_data;
for (auto p : ctx->instance->getParameters())
if (p->getValue() != p->getDefaultValue())
p->setValue(p->getDefaultValue());
}
void clap_juce_host_ext_params_request_flush(const clap_host_t *host) {
// TODO: implement
}
clap_host_params_t host_ext_params {
clap_juce_host_ext_params_rescan,
clap_juce_host_ext_params_clear,
clap_juce_host_ext_params_request_flush
};
// state support
void clap_juce_host_ext_state_mark_dirty(const clap_host_t *host) {
// TODO: implement
}
clap_host_state_t host_ext_state{ clap_juce_host_ext_state_mark_dirty };
// clap_host_t implementation
const void* clap_juce_host_get_extension(const clap_host_t* host, const char* name) {
if (strcmp(name, CLAP_EXT_LOG) == 0)
return &host_ext_log;
if (strcmp(name, CLAP_EXT_AUDIO_PORTS) == 0)
return &host_ext_audio_ports;
if (strcmp(name, CLAP_EXT_NOTE_PORTS) == 0)
return &host_ext_note_ports;
if (strcmp(name, CLAP_EXT_THREAD_CHECK) == 0)
return &host_ext_thread_check;
if (strcmp(name, CLAP_EXT_THREAD_POOL) == 0)
return &host_ext_thread_pool;
if (strcmp(name, CLAP_EXT_AUDIO_PORTS_CONFIG) == 0)
return &host_ext_audio_ports_config;
//if (strcmp(name, CLAP_EXT_TIMER_SUPPORT) == 0)
// return &host_ext_timer_support;
if (strcmp(name, CLAP_EXT_POSIX_FD_SUPPORT) == 0)
return nullptr;//&host_ext_posix_fd_support;
if (strcmp(name, CLAP_EXT_FILE_REFERENCE) == 0)
return nullptr;//&host_ext_file_reference;
if (strcmp(name, CLAP_EXT_LATENCY) == 0)
return &host_ext_latency;
if (strcmp(name, CLAP_EXT_GUI) == 0)
return &host_ext_gui;
if (strcmp(name, CLAP_EXT_PARAMS) == 0)
return &host_ext_params;
if (strcmp(name, CLAP_EXT_STATE) == 0)
return &host_ext_state;
return nullptr;
}
void clap_juce_host_request_restart(const clap_host_t* host) {
auto ctx = (CLAPFactoryContext*) host->host_data;
auto playHead = ctx->instance->getPlayHead();
if (playHead) {
playHead->transportPlay(false);
playHead->transportPlay(true);
}
}
void clap_juce_host_request_process(const clap_host_t* host) {
auto ctx = (CLAPFactoryContext*) host->host_data;
auto playHead = ctx->instance->getPlayHead();
if (playHead)
playHead->transportPlay(true);
}
void clap_juce_host_request_callback(const clap_host_t* host) {
auto ctx = (CLAPFactoryContext*) host->host_data;
juce::MessageManager::callAsync([ctx]() {
ctx->plugin->on_main_thread(ctx->plugin);
});
}
} // namespaces
#ifndef JUCE_CLAPAUDIOPLUGINFORMAT_H
#define JUCE_CLAPAUDIOPLUGINFORMAT_H
#include "JuceHeader.h"
#include <dlfcn.h>
#include "include/clap/clap.h"
#include "include/clap/helpers/event-list.hh"
#ifdef _OPENMP
#include <omp.h>
#endif
namespace clap_juce_hosting {
const void* clap_juce_host_get_extension(const clap_host_t* host, const char* name);
void clap_juce_host_request_restart(const clap_host_t*);
void clap_juce_host_request_process(const clap_host_t*);
void clap_juce_host_request_callback(const clap_host_t*);
class CLAPFactoryContext;
class CLAPHostMultiTimer : public juce::MultiTimer {
CLAPFactoryContext* ctx;
public:
explicit CLAPHostMultiTimer(CLAPFactoryContext* ctx) : ctx(ctx) {}
void timerCallback (int timerID) override;
JUCE_LEAK_DETECTOR (CLAPHostMultiTimer)
};
// FIXME: split factory context and instance context
class CLAPInstanceContext {
public:
CLAPFactoryContext* factory;
};
class CLAPAudioPluginInstance;
class Logger {
CLAPFactoryContext* ctx;
#define MAX_LOG_MESSAGE_SIZE 4096
struct LogEntry {
clap_log_severity severiry;
const char* message; // point to an entry in cached_log_messages
};
#define MAX_LOG_ENTRIES_PER_CYCLE 128
char cached_log_messages[MAX_LOG_ENTRIES_PER_CYCLE][MAX_LOG_MESSAGE_SIZE];
LogEntry cached_log_entries[MAX_LOG_ENTRIES_PER_CYCLE];
volatile size_t cached_log_entries_count{0};
const char* reached_max_log_entries = "The logger encountered too many log entries to process per its non-realtime cycle. Further log entries per this cycle are discarded.";
static void default_logging(clap_log_severity severity, const char *msg) {
// TODO: make it RT-ready i.e. just enqueue it for non-RT logging thread.
printf("CLAPJUCEHost [%s]: ", getLogLevelDescription(severity));
puts(msg);
}
public:
explicit Logger(CLAPFactoryContext* context) : ctx(context) {}
static const char* getLogLevelDescription(clap_log_severity severity) {
switch (severity) {
case CLAP_LOG_FATAL: return "FATAL";
case CLAP_LOG_ERROR: return "ERROR";
case CLAP_LOG_WARNING: return "WARNING";
case CLAP_LOG_INFO: return "INFO";
case CLAP_LOG_DEBUG: return "DEBUG";
case CLAP_LOG_PLUGIN_MISBEHAVING: return "PLUGIN";
case CLAP_LOG_HOST_MISBEHAVING: return "HOST";
default: return "";
}
}
void addEntry(clap_log_severity severity, const char* msg) {
if (cached_log_entries_count == MAX_LOG_ENTRIES_PER_CYCLE)
return; // already reached at the maximum number of cached log entries.
if (cached_log_entries_count++ == MAX_LOG_ENTRIES_PER_CYCLE) {
cached_log_entries[MAX_LOG_ENTRIES_PER_CYCLE - 1] = LogEntry{CLAP_LOG_WARNING, reached_max_log_entries};
return;
}
size_t at = cached_log_entries_count - 1;
strncpy(cached_log_messages[at], msg, MAX_LOG_MESSAGE_SIZE);
cached_log_entries[at] = LogEntry{severity, cached_log_messages[at]};
// TODO: launch a non-RT thread that processes flushLogs(). calling it violates RT safety.
flushLogs();
}
void flushLogs() {
size_t numEntries = cached_log_entries_count;
for (size_t i = 0; i < numEntries; i++) {
auto &e = cached_log_entries[i];
log(e.severiry, e.message);
}
for (size_t i = numEntries; i < cached_log_entries_count; i++)
cached_log_entries[i - numEntries] = cached_log_entries[i];
cached_log_entries_count -= numEntries;
}
std::function<void(clap_log_severity, const char*)> log =
[](clap_log_severity severity, const char *msg) { default_logging(severity, msg); };
JUCE_LEAK_DETECTOR (Logger)
};
class CLAPFactoryContext {
public:
void* dl{nullptr};
const clap_plugin_entry_t * entrypoint{nullptr};
const clap_plugin_factory_t *factory{nullptr};
clap_host_t host{};
const clap_plugin_t *plugin{nullptr};
CLAPAudioPluginInstance* instance{nullptr};
Logger logger{this};
// they are the actual audio buffers, allocated for all inputs, managed in flat arrays.
void* audio_input_buffers[128];
void* audio_output_buffers[128];
#define DEFAULT_PROCESS_EVENT_BUFFER_SIZE 524288
#define DEFAULT_EDITOR_EVENT_BUFFER_SIZE 8192
size_t process_event_buffer_size{DEFAULT_PROCESS_EVENT_BUFFER_SIZE};
size_t editor_event_buffer_size{DEFAULT_EDITOR_EVENT_BUFFER_SIZE};
// One is passed to clap_process_t. Another holds the input events from the UI as well as hold non-RT output events.
// Since EventList is not a sorted list, we have to merge two inputs (editor_event_list and MidiBuffer) at procss()-time.
clap::helpers::EventList proc_event_list{};
clap::helpers::EventList editor_event_list{};
//void* events_input_buffer{nullptr};
//void* events_output_buffer{nullptr};
// they are assigned to clap_process_t
clap_audio_buffer clap_audio_input_array[128];
clap_audio_buffer clap_audio_output_array[128];
clap_process_t process{0, 0, nullptr, clap_audio_input_array, clap_audio_output_array, 0, 0, nullptr, nullptr};
const clap_plugin_audio_ports_t *audio_ports_ext{nullptr};
const clap_plugin_note_ports_t *note_ports_ext{nullptr};
const clap_plugin_tail_t *tail_ext{nullptr};
const clap_plugin_state_t *state_ext{nullptr};
const clap_plugin_params *params_ext{nullptr};
const clap_plugin_gui_t *gui_ext{nullptr};
const clap_plugin_latency_t *latency_ext{nullptr};
const clap_plugin_thread_pool_t *thread_pool_ext{nullptr};
juce::Thread::ThreadID audio_thread_id{nullptr};
CLAPHostMultiTimer timer{this};
int last_timer_id{0};
bool is_processing{false};
juce::ThreadPool thread_pool{};
~CLAPFactoryContext() {
if (plugin)
plugin->destroy(plugin);
if (entrypoint)
entrypoint->deinit();
if (dl)
dlclose(dl);
}
bool realtimeParallelFor(int numTasks) {
// It turned out that "thread pool" is not really about thread pool like LV2 Worker, but rather about realtime
// parallel processing. That is, the host can prepare multiple RT threads depending on the CPU cores and let them
// perform the designated work as the plugin's thread_pool `exec()`.
// They have to finish within RT throttle, and returns back to the host.
// The host only cares about running the tasks (fires and forgets), and does not care if the plugin actually
// completed the job or not. `request_exec()` is called by the plugin, and plugins can either wait for the results
// from those tasks, e.g. by spinning, or continue without the results (e.g. "unprepared voices are ignored").
//
// So, to implement this functionality we would need some implementation for platform-agnostic realtime
// parallel processing.
#ifdef _OPENMP
if (thread_pool_ext != nullptr && !omp_in_parallel() && omp_get_max_threads() - omp_get_num_threads() >= numTasks) {
#pragma omp parallel for
for (int i = 0; i < numTasks; i++)
thread_pool_ext->exec(plugin, i); // We do not (cannot) care if the actual task was completed in hard RT.
}
#pragma omp end parallel
return true;
#else
return false;
#endif
}
JUCE_LEAK_DETECTOR (CLAPFactoryContext)
};
uint32_t clap_juce_host_in_events_size(const struct clap_input_events *list);
const clap_event_header_t *clap_juce_host_in_events_get(const struct clap_input_events *list, uint32_t index);
bool clap_juce_host_out_events_try_push(const struct clap_output_events *list, const clap_event_header_t *event);
// -----------------------------------------------------------------------------------------
// HostedAudioPrpcessorParameter implementation
// -----------------------------------------------------------------------------------------
class CLAPHostedAudioProcessorParameter : public HostedAudioProcessorParameter {
CLAPFactoryContext* ctx;
clap_input_events_t set_value_input_events{};
public:
struct HostedParameterNewValue {
CLAPHostedAudioProcessorParameter *parameter;
float new_value;
int32_t sample_position_since_last_process;
};
CLAPAudioPluginInstance* instance;
clap_param_info_t info;
clap_event_param_value set_value_event{};
CLAPHostedAudioProcessorParameter(CLAPAudioPluginInstance* instance, CLAPFactoryContext* ctx, clap_param_info_t& info)
: ctx(ctx), instance(instance), info(info) {
}
float getValue() const override {
double value;
return ctx->params_ext->get_value(ctx->plugin, info.id, &value) ? (float) value : 0;
}
void setValue(float newValue) override;
float getDefaultValue() const override {
return (float) info.default_value;
}
String getName(int maximumStringLength) const override {
return String(info.name).substring(0, maximumStringLength);
}
String getLabel() const override {
return String(info.name);
}
float getValueForText(const String &text) const override {
double ret;
return ctx->params_ext->text_to_value(ctx->plugin, info.id, text.toRawUTF8(), &ret) ? (float) ret : 0;
}
String getParameterID() const override {
return String{info.id};
}
JUCE_LEAK_DETECTOR (CLAPHostedAudioProcessorParameter)
};
// -----------------------------------------------------------------------------------------
// AudioProcessorEditor implementation
// -----------------------------------------------------------------------------------------
class CLAPHostAudioProcessorEditor : public AudioProcessorEditor {
class EditorComponentListener : public ComponentListener{
CLAPHostAudioProcessorEditor* editor;
public:
explicit EditorComponentListener(CLAPHostAudioProcessorEditor* editor) : editor(editor) {}
void componentVisibilityChanged(Component &component) override {
if (editor->isVisible())
editor->ctx->gui_ext->show(editor->ctx->plugin);
else
editor->ctx->gui_ext->hide(editor->ctx->plugin);
}
void componentParentHierarchyChanged(Component &component) override {
if (&component == editor) {
if (editor->getPeer())
editor->onAttachedToNewParent();
else
editor->onDetachedFromParent();
}
}
};
CLAPFactoryContext *ctx;
clap_window_t window{};
bool floating{false};
EditorComponentListener component_listener{this};
public:
CLAPHostAudioProcessorEditor(AudioProcessor *processor, CLAPFactoryContext *ctx)
: AudioProcessorEditor(processor), ctx(ctx) {
#if JUCE_WINDOWS
const char *default_api = CLAP_WINDOW_API_WIN32;
#elif JUCE_MAC
const char *default_api = CLAP_WINDOW_API_COCOA;
#else
const char *default_api = CLAP_WINDOW_API_X11;
#endif
window.api = default_api;
if (!ctx->gui_ext->get_preferred_api(ctx->plugin, &window.api, &floating)) {
window.api = default_api;
floating = false;
assert(ctx->gui_ext->is_api_supported(ctx->plugin, window.api, floating));
}
assert(ctx->gui_ext->create(ctx->plugin, window.api, floating));
uint32_t w, h;
ctx->gui_ext->get_size(ctx->plugin, &w, &h);
setSize((int) w, (int) h);
if (floating) {
ctx->gui_ext->set_transient(ctx->plugin, &window);
ctx->gui_ext->suggest_title(ctx->plugin, "JUCE CLAP Host");
} else {
this->setTitle("JUCE CLAP Host");
ctx->gui_ext->set_scale(ctx->plugin, getDesktopScaleFactor());
ctx->gui_ext->show(ctx->plugin);
}
addComponentListener(&component_listener);
}
~CLAPHostAudioProcessorEditor() override {
ctx->gui_ext->destroy(ctx->plugin);
removeComponentListener(&component_listener);
processor.editorBeingDeleted(this);
AudioProcessorEditor::~AudioProcessorEditor();
}
void onAttachedToNewParent() {
auto h = getWindowHandle();
if (h == nullptr)
return;
if (!window.ptr)
ctx->gui_ext->show(ctx->plugin);
window.ptr = h;
if (ctx->gui_ext->set_parent(ctx->plugin, &window)) {
auto c = (DocumentWindow*) getTopLevelComponent();
//auto newRect = getBounds().withTrimmedTop(c->getTitleBarHeight());
//setBounds(newRect);
}
}
void onDetachedFromParent() {
#if JUCE_WINDOWS
window.win32 = nullptr;
#elif JUCE_MAC
window.cocoa = nullptr;
#else
window.x11 = 0;
#endif
ctx->gui_ext->set_parent(ctx->plugin, &window);
}
bool onShowRequested() {
// if it is parented, then parent need to be shown. Otherwise, just indicate the editor itself to show up.
if (getPeer())
getPeer()->setVisible(true);
else
setVisible(true);
return true;
}
bool onHideRequested() {
// if it is parented, then parent need to be hidden. Otherwise, just indicate the editor itself to hide.
if (getPeer())
getPeer()->setVisible(false);
else
setVisible(false);
return true;
}
void updateResizeHints() {
clap_gui_resize_hints_t hints;
ctx->gui_ext->get_resize_hints(ctx->plugin, &hints);
auto screenBounds = getScreenBounds();
setResizeLimits(
hints.can_resize_horizontally ? getConstrainer()->getMinimumWidth() : getWidth(),
hints.can_resize_vertically ? getConstrainer()->getMinimumHeight() : getHeight(),
screenBounds.getWidth(), screenBounds.getHeight());
if (hints.preseve_aspect_ratio) // otherwise not supported
setScaleFactor(hints.aspect_ratio_height);
}
JUCE_LEAK_DETECTOR (CLAPHostAudioProcessorEditor)
};
// -----------------------------------------------------------------------------------------
// AudioPluginInstance implementation
// -----------------------------------------------------------------------------------------
class CLAPAudioPluginInstance : public AudioPluginInstance {
friend class CLAPHostedAudioProcessorParameter;
std::unique_ptr<CLAPFactoryContext> ctx;
PluginDescription desc;
String name;
double sample_rate;
int buffer_size;
bool has_midi_in, has_midi_out;
public:
CLAPAudioPluginInstance(std::unique_ptr<CLAPFactoryContext> context, const PluginDescription& description, double initialSampleRate, int initialBufferSize)
: AudioPluginInstance(configureBusesProperties(context.get())),
ctx(std::move(context)),
sample_rate(initialSampleRate),
buffer_size(initialBufferSize) {
desc = PluginDescription(description);
ctx->instance = this;
ctx->note_ports_ext = static_cast<const clap_plugin_note_ports_t *>(
ctx->plugin->get_extension(ctx->plugin, CLAP_EXT_NOTE_PORTS));
ctx->audio_ports_ext = (clap_plugin_audio_ports_t *) ctx->plugin->get_extension(
ctx->plugin, CLAP_EXT_AUDIO_PORTS);
ctx->tail_ext = (clap_plugin_tail_t *) ctx->plugin->get_extension(
ctx->plugin, CLAP_EXT_TAIL);
ctx->state_ext = (clap_plugin_state_t *) ctx->plugin->get_extension(
ctx->plugin, CLAP_EXT_STATE);
clap_note_port_info_t port;
has_midi_in = ctx->note_ports_ext->count(ctx->plugin, true) && ctx->note_ports_ext->get(ctx->plugin, 0, true, &port) && (port.supported_dialects & CLAP_EVENT_MIDI) != 0;
has_midi_out = ctx->note_ports_ext->count(ctx->plugin, false) && ctx->note_ports_ext->get(ctx->plugin, 0, false, &port) && (port.supported_dialects & CLAP_EVENT_MIDI) != 0;
buildParameters();
ctx->gui_ext = static_cast<const clap_plugin_gui_t *>(ctx->plugin->get_extension(ctx->plugin, CLAP_EXT_GUI));
ctx->latency_ext = static_cast<const clap_plugin_latency_t *>(ctx->plugin->get_extension(ctx->plugin, CLAP_EXT_LATENCY));
ctx->thread_pool_ext = static_cast<const clap_plugin_thread_pool_t *>(ctx->plugin->get_extension(ctx->plugin, CLAP_EXT_THREAD_POOL));
}
static BusesProperties configureBusesProperties(CLAPFactoryContext* ctx) {
BusesProperties ret{};
ctx->audio_ports_ext = (clap_plugin_audio_ports_t *) ctx->plugin->get_extension(
ctx->plugin, CLAP_EXT_AUDIO_PORTS);
for (uint32_t i = 0, n = ctx->audio_ports_ext->count(ctx->plugin, true); i < n; i++) {
clap_audio_port_info info;
ctx->audio_ports_ext->get(ctx->plugin, i, true, &info);
if (!strcmp(info.port_type, CLAP_PORT_MONO))
ret.inputLayouts.add(BusProperties{info.name, AudioChannelSet::mono(), (info.flags & CLAP_AUDIO_PORT_IS_MAIN) != 0});
else if (!strcmp(info.port_type, CLAP_PORT_STEREO))
ret.inputLayouts.add(BusProperties{info.name, AudioChannelSet::stereo(), (info.flags & CLAP_AUDIO_PORT_IS_MAIN) != 0});
// TODO: else: implement
}
for (uint32_t i = 0, n = ctx->audio_ports_ext->count(ctx->plugin, false); i < n; i++) {
clap_audio_port_info info;
ctx->audio_ports_ext->get(ctx->plugin, i, false, &info);
if (!strcmp(info.port_type, CLAP_PORT_MONO))
ret.outputLayouts.add(BusProperties{info.name, AudioChannelSet::mono(), (info.flags & CLAP_AUDIO_PORT_IS_MAIN) != 0});
else if (!strcmp(info.port_type, CLAP_PORT_STEREO))
ret.outputLayouts.add(BusProperties{info.name, AudioChannelSet::stereo(), (info.flags & CLAP_AUDIO_PORT_IS_MAIN) != 0});
// TODO: else: implement
}
return ret;
}
void buildParameters() {
ctx->params_ext = static_cast<const clap_plugin_params *>(ctx->plugin->get_extension(ctx->plugin, CLAP_EXT_PARAMS));
AudioProcessorParameterGroup tree{};
for (uint32_t i = 0, n = ctx->params_ext->count(ctx->plugin); i < n; i++) {
clap_param_info_t info;
if (!ctx->params_ext->get_info(ctx->plugin, i, &info)) {
ctx->logger.addEntry(CLAP_LOG_ERROR, String::formatted("Failed to retrieve plugin parameter of \"%s\", at: %d", getName().toRawUTF8(), i).toRawUTF8());
continue;
}
if (info.module[0] == 0) {
tree.addChild(std::make_unique<CLAPHostedAudioProcessorParameter>(this, ctx.get(), info));
} else {
static String root{"/"};
AudioProcessorParameterGroup *g = &tree;
if ((info.module[0] == '/' && info.module[1] == 0))
g = getOrCreateGroup(g, root);
else {
StringArray paths{};
std::stringstream ss{info.module};
std::string s{};
while (getline(ss, s, '/'))
if (!s.empty())
paths.add(s);
for (auto path: paths)
if (!path.isEmpty())
g = getOrCreateGroup(g, path);
}
g->addChild(std::make_unique<CLAPHostedAudioProcessorParameter>(this, ctx.get(), info));
}
}
setHostedParameterTree(std::move(tree));
}
static AudioProcessorParameterGroup* getOrCreateGroup(AudioProcessorParameterGroup *parent, String& subgroupName) {
auto children = parent->getSubgroups(false);
for (const AudioProcessorParameterGroup* child : children)
if (child->getName() == subgroupName)
return const_cast<AudioProcessorParameterGroup*>(child);
auto newChild = std::make_unique<AudioProcessorParameterGroup>(subgroupName, subgroupName, "/");
auto ret = newChild.get();
parent->addChild(std::move(newChild));
return ret;
}
const String getName() const override { return desc.descriptiveName; }
void prepareToPlay (double sampleRate,
int maximumExpectedSamplesPerBlock) override {
sample_rate = sampleRate;
for (int i = 0, n = getTotalNumInputChannels(); i < n; i++) {
if (isUsingDoublePrecision())
ctx->audio_input_buffers[i] = calloc(maximumExpectedSamplesPerBlock, sizeof(double));
else
ctx->audio_input_buffers[i] = calloc(maximumExpectedSamplesPerBlock, sizeof(float));
}
ctx->audio_input_buffers[getTotalNumInputChannels()] = nullptr;
for (int i = 0, n = getTotalNumOutputChannels(); i < n; i++) {
if (isUsingDoublePrecision())
ctx->audio_output_buffers[i] = calloc(maximumExpectedSamplesPerBlock, sizeof(double));
else
ctx->audio_output_buffers[i] = calloc(maximumExpectedSamplesPerBlock, sizeof(float));
}
ctx->audio_output_buffers[getTotalNumOutputChannels()] = nullptr;
ctx->proc_event_list.reserveHeap(ctx->process_event_buffer_size);
ctx->editor_event_list.reserveHeap(ctx->editor_event_buffer_size);
ctx->process.in_events = ctx->proc_event_list.clapInputEvents();
ctx->process.out_events = ctx->proc_event_list.clapOutputEvents();
// set up audio buffers in clap_process_t (in and out).
ctx->process.audio_inputs_count = (uint32_t) ctx->audio_ports_ext->count(ctx->plugin, true);
auto audioInputs = ctx->clap_audio_input_array;
int inChannel = 0;
for (uint32_t i = 0, n = ctx->process.audio_inputs_count; i < n; i++) {
if (!getBus(true, i)->isEnabled())
continue;
auto &inputs = audioInputs[i];
clap_audio_port_info info{};
ctx->audio_ports_ext->get(ctx->plugin, i, true, &info);
inputs.channel_count = info.channel_count;
if (isUsingDoublePrecision()) {
inputs.data64 = (double**) calloc(info.channel_count, sizeof(double*));
for (uint32_t ch = 0, nCh = info.channel_count; ch < nCh; ch++)
inputs.data64[ch] = (double *) ctx->audio_input_buffers[inChannel++];
} else {
inputs.data32 = (float**) calloc(info.channel_count, sizeof(float*));
for (uint32_t ch = 0, nCh = info.channel_count; ch < nCh; ch++)
inputs.data32[ch] = (float *) ctx->audio_input_buffers[inChannel++];
}
}
ctx->process.audio_inputs = audioInputs;
ctx->process.audio_outputs_count = (uint32_t) ctx->audio_ports_ext->count(ctx->plugin, false);
clap_audio_buffer_t *outputs = ctx->process.audio_outputs;
int outChannel = 0;
for (uint32_t i = 0, n = ctx->process.audio_outputs_count; i < n; i++) {
if (!getBus(false, i)->isEnabled())
continue;
clap_audio_port_info info{};
ctx->audio_ports_ext->get(ctx->plugin, i, false, &info);
outputs[i].channel_count = info.channel_count;
if (isUsingDoublePrecision()) {
outputs[i].data64 = (double**) calloc(info.channel_count, sizeof(double*));
for (uint32_t ch = 0, nCh = info.channel_count; ch < nCh; ch++)
outputs[i].data64[ch] = (double *) ctx->audio_output_buffers[outChannel++];
} else {
outputs[i].data32 = (float**) calloc(info.channel_count, sizeof(float*));
for (uint32_t ch = 0, nCh = info.channel_count; ch < nCh; ch++)
outputs[i].data32[ch] = (float *) ctx->audio_output_buffers[outChannel++];
}
}
ctx->process.audio_outputs = ctx->clap_audio_output_array;
// We are ready to start processing.
ctx->plugin->activate(ctx->plugin, sampleRate, 1, static_cast<uint32_t>(maximumExpectedSamplesPerBlock));
}
void releaseResources() override {
// TODO: how can we call stop_processing() ? It is supposed to be invoked on audio thread, but releaseResources() is not invoked there.
//ctx->plugin->stop_processing(ctx->plugin);
ctx->is_processing = false;
for (int i = 0, n = getTotalNumInputChannels(); i < n; i++)
if (ctx->audio_input_buffers[i])
free(ctx->audio_input_buffers[i]);
for (int i = 0, n = getTotalNumOutputChannels(); i < n; i++)
if (ctx->audio_output_buffers[i])
free(ctx->audio_output_buffers[i]);
//if (ctx->events_input_buffer)
// free(ctx->events_input_buffer);
//if (ctx->events_output_buffer)
// free(ctx->events_output_buffer);
ctx->proc_event_list.clear();
ctx->editor_event_list.clear();
ctx->plugin->deactivate(ctx->plugin);
}
void processBlock (AudioBuffer<float>& buffer,
MidiBuffer& midiMessages) override {
if (!ctx->audio_thread_id)
ctx->audio_thread_id = juce::Thread::getCurrentThreadId();
if (!ctx->is_processing) {
ctx->is_processing = true;
ctx->plugin->start_processing(ctx->plugin);
}
// Merge MidiBuffer and editor_event_list inputs into proc_event_list inputs.
{
size_t editorIndex = 0, editorSize = ctx->editor_event_list.size();
int64_t editorEventPosition = 0, lastMidiPosition = -1;
ctx->proc_event_list.empty();
for (auto midiMessage : midiMessages) {
// we can skip editor events while the MIDI in events are at the same position
// (consider hundreds or thousands of MIDI events at position 0 at the same time...)
if (lastMidiPosition < midiMessage.samplePosition) {
// insert editor events that are between the previous event and current event, samplePosition wise.
while (editorIndex < editorSize) {
auto evt = ctx->editor_event_list.get(editorIndex);
editorEventPosition = evt->time;
if (editorEventPosition <= (int64_t) midiMessage.samplePosition)
break;
evt->flags |= CLAP_EVENT_IS_LIVE;
ctx->proc_event_list.push(evt);
editorIndex++;
}
}
// convert MIDI events to CLAP events and insert into proc_event_list.
if (midiMessage.numBytes > 0 && midiMessage.data[0] == 0xF0) {
clap_event_midi_sysex msg{
clap_event_header_t{(uint32_t) sizeof(clap_event_midi_sysex), (uint32_t) midiMessage.samplePosition, CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_MIDI_SYSEX, 0},
0, midiMessage.data, (uint32_t) midiMessage.numBytes};
ctx->proc_event_list.push(&msg.header);
} else {
auto inMsg = midiMessage.getMessage();
/*
if (inMsg.isNoteOn()) {
clap_event_note msg{
clap_event_header_t{(uint32_t) sizeof(clap_event_midi), (uint32_t) midiMessage.samplePosition, CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_ON, 0},
-1, 0, (short) inMsg.getChannel(), (short) inMsg.getNoteNumber(), inMsg.getVelocity() / 127.0};
ctx->proc_event_list.push(&msg.header);
} else if (inMsg.isNoteOff()) {
clap_event_note msg{
clap_event_header_t{(uint32_t) sizeof(clap_event_midi), (uint32_t) midiMessage.samplePosition, CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_NOTE_OFF, 0},
-1, 0, (short) inMsg.getChannel(), (short) inMsg.getNoteNumber(), inMsg.getVelocity() / 127.0};
ctx->proc_event_list.push(&msg.header);
} else*/ {
clap_event_midi msg{
clap_event_header_t{(uint32_t) sizeof(clap_event_midi), (uint32_t) midiMessage.samplePosition, CLAP_CORE_EVENT_SPACE_ID, CLAP_EVENT_MIDI, 0},0};
memcpy(msg.data, midiMessage.data, 3);
ctx->proc_event_list.push(&msg.header);
}
}
}
// push remaining events to proc_event_list (the loop above does nothing if MIDI list is empty).
while (editorIndex < editorSize) {
auto evt = ctx->editor_event_list.get(editorIndex++);
ctx->proc_event_list.push(evt);
}
ctx->editor_event_list.empty();
}
ctx->process.frames_count = (uint32_t) buffer.getNumSamples();
// Assign the JUCE audio buffer pointers to the CLAP audio buffer pointers in clap_process_t.
int inChannel = 0;
for (int i = 0, n = getBusCount(true); i < n; i++) {
if (!getBus(true, i)->isEnabled())
continue;
for (int ch = 0, nCh = getChannelCountOfBus(true, i); ch < nCh; ch++) {
if (isUsingDoublePrecision())
ctx->process.audio_inputs[i].data64[ch] = (double*) (void*) buffer.getReadPointer(inChannel);
else
ctx->process.audio_inputs[i].data32[ch] = (float*) buffer.getReadPointer(inChannel);
inChannel++;
}
}
int outChannel = 0;
for (int i = 0, n = getBusCount(false); i < n; i++) {
if (!getBus(false, i)->isEnabled())
continue;
for (int ch = 0, nCh = getChannelCountOfBus(false, i); ch < nCh; ch++) {
if (isUsingDoublePrecision()) {
ctx->process.audio_outputs[i].data64[ch] = (double*) (void*) buffer.getWritePointer(outChannel);//(double*) ctx->audio_output_buffers[outChannel];
} else {
ctx->process.audio_outputs[i].data32[ch] = (float*) buffer.getWritePointer(outChannel); //(float*) ctx->audio_output_buffers[outChannel];
}
outChannel++;
}
}
// FIXME: remove this?
for (int i = 0, n = getTotalNumInputChannels(); i < n; i++)
memset(ctx->audio_input_buffers[i], isUsingDoublePrecision() ? sizeof(double) : sizeof(float), buffer.getNumSamples());
for (int i = 0, n = getTotalNumOutputChannels(); i < n; i++)
memset(ctx->audio_output_buffers[i], isUsingDoublePrecision() ? sizeof(double) : sizeof(float), buffer.getNumSamples());
// Let the plugin process it.
ctx->plugin->process(ctx->plugin, &ctx->process);
{
midiMessages.clear();
// TODO: convert CLAP event outputs to MidiBuffer as outputs (but HOW?)
}
/*
// JUCE output pointers may be the same pointers as input pointers, and we are unsure if the CLAP plugin
// anticipates that. So memcpy them instead.
outChannel = 0;
for (int i = 0, n = getBusCount(false); i < n; i++) {
if (!getBus(false, i)->isEnabled())
continue;
for (int ch = 0, nCh = getChannelCountOfBus(false, i); ch < nCh; ch++) {
if (isUsingDoublePrecision())
memcpy(buffer.getWritePointer(outChannel), ctx->audio_output_buffers[outChannel],
sizeof(double) * (size_t) buffer.getNumSamples());
else
memcpy(buffer.getWritePointer(outChannel), ctx->audio_output_buffers[outChannel],
sizeof(float) * (size_t) buffer.getNumSamples());
outChannel++;
}
}
*/
}
double getTailLengthSeconds() const override {
return ctx->tail_ext->get(ctx->plugin) * 1.0 / sample_rate;
}
bool acceptsMidi() const override { return has_midi_in; }
bool producesMidi() const override {return has_midi_out; }
AudioProcessorEditor* createEditor() override {
return ctx->gui_ext ? new CLAPHostAudioProcessorEditor(this, ctx.get()) : nullptr;
}
bool hasEditor() const override {
return ctx->gui_ext != nullptr;
}
int getNumPrograms() override {
return 0; // TODO: implement
}
int getCurrentProgram() override {
return 0; // TODO: implement
}
void setCurrentProgram (int index) override {
// TODO: implement
}
const String getProgramName (int index) override {
return ""; // TODO: implement
}
void changeProgramName (int index, const String& newName) override {
// TODO: implement
}
static int64_t clap_juce_host_write_to_memory_block(const clap_ostream_t *stream, const void *buffer, uint64_t size) {
auto dst = (juce::MemoryBlock*) stream->ctx;
dst->append(buffer, (size_t) size);
return (int64_t) size;
}
void getStateInformation (juce::MemoryBlock& destData) override {
if (!ctx->state_ext)
return; // state is not supported(!?)
clap_ostream_t stream{&destData, clap_juce_host_write_to_memory_block};
ctx->state_ext->save(ctx->plugin, &stream);
}
struct JUCEStateData {
const void* data;
int sizeInBytes;
int current;
};
static int64_t clap_juce_host_read_from_state_data(const struct clap_istream *stream, void *buffer, uint64_t size) {
auto juce = (JUCEStateData*) stream->ctx;
auto remaining = juce->sizeInBytes - juce->current;
if (remaining <= 0)
return 0;
auto actualSize = remaining > (int) size ? (int) size : remaining;
memcpy(buffer, (uint8_t*) juce->data + juce->current, (size_t) actualSize);
juce->current += actualSize;
return actualSize;
}
void setStateInformation (const void* data, int sizeInBytes) override {
if (!ctx->state_ext)
return; // state is not supported(!?)
JUCEStateData juce{data, sizeInBytes, 0};
clap_istream_t stream{&juce, clap_juce_host_read_from_state_data};
ctx->state_ext->load(ctx->plugin, &stream);
}
void fillInPluginDescription (PluginDescription& dst) const override {
dst.loadFromXml(*desc.createXml());
}
JUCE_LEAK_DETECTOR (CLAPAudioPluginInstance)
};
// -----------------------------------------------------------------------------------------
// AudioPluginFormat implementation
// -----------------------------------------------------------------------------------------
class CLAPAudioPluginFormat : public AudioPluginFormat {
public:
// CLAPAudioPluginFormat specifics.
CLAPAudioPluginFormat (const char* hostName = "CLAPAudioPluginFormat",
const char* hostVendor = "atsushieno",
const char* hostUrl = "https://github.com/atsushieno/clap-juce-hosting",
const char* hostVersion = "1.0")
: host_name(hostName), host_vendor(hostVendor), host_url(hostUrl), host_version(hostVersion) {
}
const char *host_name;
const char *host_vendor;
const char *host_url;
const char *host_version;
// JUCE overrides
[[nodiscard]] String getName() const override { return "CLAP"; }
clap_host_t createClapHost(CLAPFactoryContext* ctx) {
return clap_host_t{/*.version = */ ctx->entrypoint->clap_version,
/*.host_data = */ ctx,
/* .name = */ host_name,
/* .vendor = */ host_vendor,
/* .url = */ host_url,
/* .version = */ host_version,
/* .get_extension = */ clap_juce_host_get_extension,
/* .request_restart = */ clap_juce_host_request_restart,
/* .request_process = */ clap_juce_host_request_process,
/* .request_callback = */ clap_juce_host_request_callback
};
}
bool isInstrument(const char** features) {
if (!features)
return false;
int i = 0;
while (true) {
if (!features[i])
return false;
if (strcmp(features[i], "instrument") == 0)
return true;
i++;
}
}
static String getCategoryFromFeatures(const char** features) {
if (!features)
return "";
int i = 0;
String ret{};
while (true) {
if (!features[i])
return ret;
ret += (ret.length() > 0 ? "," : "");
ret += features[i];
i++;
}
}
typedef struct scan_on_main_thread_info {
CLAPAudioPluginFormat* format;
OwnedArray<PluginDescription>& results;
String fileOrIdentifier;
} scan_on_main_thread_info_t;
static void* staticScanOnMainThread(void* userData) {
auto data = (scan_on_main_thread_info_t*) userData;
return data->format->scanOnMainThread(data->results, data->fileOrIdentifier);
}
void* scanOnMainThread(OwnedArray<PluginDescription>& results, String fileOrIdentifier) {
File file{fileOrIdentifier};
if (!file.exists())
return nullptr;
auto ctx = openContext(fileOrIdentifier);
if (!ctx->factory)
return nullptr;
auto numPlugins = ctx->factory->get_plugin_count(ctx->factory);
auto host = createClapHost(ctx.get() );
for (uint i = 0; i < numPlugins; i++) {
auto desc = ctx->factory->get_plugin_descriptor(ctx->factory, i);
if (desc) {
// While CLAP claims it supports faster plugin scanning, we have to instantiate the
// plugin to retrieve the numbers of in/out channels to implement this function...
auto plugin = ctx->factory->create_plugin(ctx->factory, &host, desc->id);
plugin->init(plugin);
if (plugin) {
auto audioPorts = (clap_plugin_audio_ports_t *) plugin->get_extension(plugin,
CLAP_EXT_AUDIO_PORTS);
PluginDescription result{};
result.name = desc->id;
result.descriptiveName = desc->name;
result.pluginFormatName = "CLAP";
result.category = getCategoryFromFeatures(desc->features);
result.manufacturerName = desc->vendor;
result.version = desc->version;
result.fileOrIdentifier = fileOrIdentifier;
result.lastFileModTime = file.getLastModificationTime();
result.lastInfoUpdateTime = file.getLastAccessTime();
result.deprecatedUid = String(desc->id).hash();
result.uniqueId = String(desc->id).hash();
result.isInstrument = isInstrument(desc->features);
result.numInputChannels = audioPorts ? (int) audioPorts->count(plugin, true) : 0;
result.numOutputChannels = audioPorts ? (int) audioPorts->count(plugin, false) : 0;
//result.hasSharedContainer = hasSharedContainer(desc->features);
//result.hasARAExtension = hasARAExtension(desc->features);
results.add(new PluginDescription(result));
plugin->destroy(plugin);
}
}
}
}
void findAllTypesForFile (OwnedArray<PluginDescription>& results,
const String& fileOrIdentifier) override {
if (fileOrIdentifier.length() == 0)
return;
scan_on_main_thread_info_t info{this, results, fileOrIdentifier};
MessageManager::getInstance()->callFunctionOnMessageThread(staticScanOnMainThread, &info);
}
bool fileMightContainThisPluginType (const String& fileOrIdentifier) override {
return fileOrIdentifier.endsWith(".clap");
}
String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override {
return File{fileOrIdentifier}.getFileNameWithoutExtension(); // TODO: implement
}
bool pluginNeedsRescanning (const PluginDescription& desc) override {
return File{desc.fileOrIdentifier}.getLastAccessTime() > desc.lastInfoUpdateTime;
}
bool doesPluginStillExist (const PluginDescription& description) override {
return File{description.fileOrIdentifier}.exists();
}
[[nodiscard]] bool canScanForPlugins() const override {
return true;
}
[[nodiscard]] bool isTrivialToScan() const override {
// > Should return true if this format is both safe and quick to scan - i.e.
// > if a file can be scanned within a few milliseconds on a background thread, without actually needing to load an executable.
return false;
}
StringArray searchPathsForPlugins (const FileSearchPath& directoriesToSearch,
bool recursive,
bool /*allowPluginsWhichRequireAsynchronousInstantiation*/) override {
StringArray results{};
for (int i = 0, n = directoriesToSearch.getNumPaths(); i < n; i++) {
for (auto& file : directoriesToSearch[i].findChildFiles(File::TypesOfFileToFind::findFiles, recursive))
if (fileMightContainThisPluginType(file.getFullPathName()))
results.add(file.getFullPathName());
}
return results;
}
FileSearchPath getDefaultLocationsToSearch() override {
auto clapPath = getenv("CLAP_PATH");
#if JUCE_WINDOWS
return FileSearchPath { clapPath ? clapPath :
String::formatted(R"(%s\CLAP\;%s\Common\CLAP)", getenv("CommonFilesFolder"), getenv("LOCALAPPDATA"))};
#elif JUCE_MAC
return FileSearchPath {clapPath ? String{clapPath}.replaceCharacter(':', ';') :
String{"/Library/Audio/Plug-Ins/CLAP/;"} + String{getenv("HOME")} + "/Audio/Plug-Ins/CLAP/"};
// FIXME: the specification looks wrong (paths order), needs verification.
#else
return FileSearchPath {clapPath ? String{clapPath}.replaceCharacter(':', ';') :
String{getenv("HOME")} + "/.clap;/usr/lib/clap"};
#endif
}
[[nodiscard]] bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const override {
return false;
}
std::unique_ptr<CLAPFactoryContext> openContext(String fileOrIdentifier) {
auto ctx = std::make_unique<CLAPFactoryContext>();
ctx->dl = dlopen(fileOrIdentifier.toRawUTF8(), RTLD_LOCAL | RTLD_LAZY);
if (ctx->dl) {
ctx->entrypoint = (const clap_plugin_entry_t *) dlsym(ctx->dl, "clap_entry");
if (ctx->entrypoint && ctx->entrypoint->init(fileOrIdentifier.toRawUTF8())) {
ctx->factory = (const clap_plugin_factory_t *) ctx->entrypoint->get_factory(CLAP_PLUGIN_FACTORY_ID);
ctx->host = createClapHost(ctx.get());
}
}
return ctx;
}
void createPluginInstance (const PluginDescription& desc, double initialSampleRate,
int initialBufferSize, PluginCreationCallback callback) override {
instancing_context_t info{this, desc, initialSampleRate, initialBufferSize, callback};
MessageManager::getInstance()->callFunctionOnMessageThread(staticCreatePluginInstance, &info);
}
private:
typedef struct instancing_context {
CLAPAudioPluginFormat *format;
const PluginDescription &desc;
double initialSampleRate;
int initialBufferSize;
PluginCreationCallback callback;
} instancing_context_t;
static void* staticCreatePluginInstance(void* userData) {
auto data = (instancing_context_t*) userData;
data->format->doCreatePluginInstance(data->desc, data->initialSampleRate, data->initialBufferSize, data->callback);
return nullptr;
}
void doCreatePluginInstance (const PluginDescription& desc, double initialSampleRate,
int initialBufferSize, PluginCreationCallback callback) {
auto ctx = openContext(desc.fileOrIdentifier);
if (ctx->factory) {
ctx->plugin = ctx->factory->create_plugin(ctx->factory, &ctx->host, desc.name.toRawUTF8());
ctx->plugin->init(ctx->plugin);
auto instance = std::make_unique<CLAPAudioPluginInstance>(
std::move(ctx), desc, initialSampleRate, initialBufferSize);
if (instance) {
callback(std::move(instance), "");
}
else
callback(nullptr, String::formatted("Failed to instantiate %s (%d)", desc.name.toRawUTF8(),
desc.uniqueId));
}
}
JUCE_LEAK_DETECTOR (CLAPAudioPluginFormat)
};
} // namespaces
#endif //JUCE_CLAPAUDIOPLUGINFORMAT_H
@atsushieno
Copy link
Author

LICENSE: MIT

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment