#include "clap/clap.h" #include #include #include #include #include #include #include #include #include static std::string *font_path; struct Note { int32_t key; int32_t note_id; int32_t channel; float phase; }; class PluginGUI { public: PluginGUI() { strcpy(counter_buffer, "0"); counter = 0; font = pgpl_font_create_from_file(font_path->c_str(), 256); pgpl_gui_theme_configure(&theme, pgpl_color_create(255, 255, 255, 255), 48, font); gui = pgpl_gui_create("PGPL Test", 320, 320, 0, 0, &theme, this); pgpl_gui_widget_add(gui, create_main_view()); pgpl_gui_run_and_show(gui, false); } ~PluginGUI() { pgpl_font_destroy(NULL, font); } inline PGPL_WindowThread *get_window() { return gui->window_thread; } private: static void on_counter_button_click(void *data) { PluginGUI *app = (PluginGUI *)data; app->counter++; snprintf(app->counter_buffer, sizeof(app->counter_buffer), "%d", app->counter); if (app->counter % 10 == 0) { pgpl_gui_theme_configure( &app->gui->theme, pgpl_color_create(rand() % 256, rand() % 256, rand() % 256, 255), 48, app->gui->theme.font); } } static void configure_widget_default(PGPL_GuiWidget *widget) { pgpl_gui_widget_configure(widget, 0, 0, true, true, true, true, PGPL_GUI_FONT_SIZE_CONTENT, NULL, false, false, false, false); } PGPL_GuiWidget *create_main_view() { PGPL_GuiButtonWidget *counter_button; PGPL_GuiTextWidget *counter; column_layout = pgpl_gui_container_layout_create( PGPL_GUI_CONTAINER_LAYOUT_DIRECTION_VERTICAL); pgpl_gui_widget_configure(&column_layout->parent, 0, 0, true, true, false, false, PGPL_GUI_FONT_SIZE_CONTENT, NULL, false, true, true, false); counter_button = pgpl_gui_button_widget_create("Click me!", on_counter_button_click); configure_widget_default(&counter_button->parent); counter = pgpl_gui_text_widget_create(counter_buffer); configure_widget_default(&counter->parent); counter->parent.font_size = PGPL_GUI_FONT_SIZE_TITLE; counter->parent.background = false; counter->parent.border = false; pgpl_gui_container_layout_widget_add(column_layout, &counter->parent); pgpl_gui_container_layout_widget_add(column_layout, &counter_button->parent); return (PGPL_GuiWidget *)column_layout; } PGPL_GuiContainerLayout *column_layout; PGPL_Gui *gui; PGPL_GuiTheme theme; PGPL_Font *font; int counter; char counter_buffer[128]; }; class PluginState { public: void process_event(const clap_event_header_t *event) { if (event->space_id == CLAP_CORE_EVENT_SPACE_ID) { if (event->type == CLAP_EVENT_NOTE_OFF || event->type == CLAP_EVENT_NOTE_CHOKE) { const clap_event_note_t *note_event = (const clap_event_note_t *)event; for (size_t i = 0; i < this->notes.size(); i++) { Note ¬e = this->notes[i]; if (note_event->note_id == note.note_id || note_event->note_id == -1) { this->notes.erase(this->notes.begin() + i--); } } } else if (event->type == CLAP_EVENT_NOTE_ON) { const clap_event_note_t *note_event = (const clap_event_note_t *)event; this->notes.push_back( {note_event->key, note_event->note_id, note_event->channel, 0.0f}); } } } void render_audio(float *outputL, float *outputR, uint32_t length) { for (uint32_t index = 0; index < length; index++) { float sum = 0.0f; for (size_t i = 0; i < this->notes.size(); i++) { Note ¬e = this->notes[i]; sum += sinf(2.0f * 3.14159f * 440.0f * exp2f((note.key - 81.0f) / 12.0f) * note.phase) * 0.2f; note.phase += 1.0f / sample_rate; note.phase -= floorf(note.phase); } outputL[index] = sum; outputR[index] = sum; } } inline void set_sample_rate(uint32_t sample_rate) { this->sample_rate = sample_rate; } inline void create_window() { gui = new PluginGUI(); } inline void destroy_window() { delete gui; } inline PGPL_WindowThread *get_window() { if (gui) { return gui->get_window(); } return nullptr; } private: uint32_t sample_rate; std::vector notes; PluginGUI *gui; }; static const char *features[] = { CLAP_PLUGIN_FEATURE_INSTRUMENT, CLAP_PLUGIN_FEATURE_SYNTHESIZER, CLAP_PLUGIN_FEATURE_STEREO, NULL, }; static const clap_plugin_descriptor_t plugin_descriptor = {CLAP_VERSION_INIT, "pluto.pgpl.test", "PGPL Plugin Test", "Patrick_Pluto", "", "", "", "1.0.0", "Testing", features}; static const clap_plugin_note_ports_t note_ports = { [](const clap_plugin_t *, bool is_input) -> uint32_t { return is_input ? 1 : 0; }, [](const clap_plugin_t *, uint32_t index, bool isInput, clap_note_port_info_t *info) -> bool { if (!isInput || index) return false; info->id = 0; info->supported_dialects = CLAP_NOTE_DIALECT_CLAP; info->preferred_dialect = CLAP_NOTE_DIALECT_CLAP; strncpy(info->name, "Note Input", sizeof(info->name)); return true; }, }; static const clap_plugin_audio_ports_t audio_ports = { [](const clap_plugin_t *, bool is_input) -> uint32_t { return is_input ? 0 : 1; }, [](const clap_plugin_t *, uint32_t index, bool isInput, clap_audio_port_info_t *info) -> bool { if (isInput || index) return false; info->id = 0; info->channel_count = 2; info->flags = CLAP_AUDIO_PORT_IS_MAIN; info->port_type = CLAP_PORT_STEREO; info->in_place_pair = CLAP_INVALID_ID; strncpy(info->name, "Audio Output", sizeof(info->name)); return true; }, }; static const clap_plugin_gui_t gui = { [](const clap_plugin_t *, const char *api, bool is_floating) -> bool { #ifdef __WIN32__ if (strcmp(api, CLAP_WINDOW_API_WIN32) == 0 && !is_floating) { #else if (strcmp(api, CLAP_WINDOW_API_X11) == 0 && !is_floating) { #endif return true; } return false; }, [](const clap_plugin_t *, const char **api, bool *is_floating) -> bool { #ifdef __WIN32__ *api = CLAP_WINDOW_API_WIN32; #else *api = CLAP_WINDOW_API_X11; #endif *is_floating = false; return true; }, [](const clap_plugin_t *plugin, const char *api, bool is_floating) -> bool { #ifdef __WIN32__ if (strcmp(api, CLAP_WINDOW_API_WIN32) == 0 && !is_floating) { #else if (strcmp(api, CLAP_WINDOW_API_X11) == 0 && !is_floating) { #endif PluginState *state = (PluginState *)plugin->plugin_data; state->create_window(); return true; } return false; }, [](const clap_plugin_t *plugin) { PluginState *state = (PluginState *)plugin->plugin_data; pgpl_window_thread_destroy(state->get_window()); state->destroy_window(); }, [](const clap_plugin_t *plugin, double scale) -> bool { PluginState *state = (PluginState *)plugin->plugin_data; pgpl_window_thread_set_scale(state->get_window(), scale); return true; }, [](const clap_plugin_t *plugin, uint32_t *width, uint32_t *height) -> bool { PluginState *state = (PluginState *)plugin->plugin_data; pgpl_window_thread_get_size(state->get_window(), width, height); return true; }, [](const clap_plugin_t *) -> bool { return true; }, [](const clap_plugin_t *, clap_gui_resize_hints_t *) -> bool { return false; }, [](const clap_plugin_t *, uint32_t *, uint32_t *) -> bool { return true; }, [](const clap_plugin_t *plugin, uint32_t width, uint32_t height) -> bool { PluginState *state = (PluginState *)plugin->plugin_data; pgpl_window_thread_set_size(state->get_window(), width, height); return true; }, [](const clap_plugin_t *plugin, const clap_window_t *window) -> bool { PluginState *state = (PluginState *)plugin->plugin_data; #ifdef __WIN32__ pgpl_window_thread_reparent_to(state->get_window(), (void *)window->win32); #else pgpl_window_thread_reparent_to(state->get_window(), (void *)&window->x11); #endif return true; }, [](const clap_plugin_t *plugin, const clap_window_t *window) -> bool { PluginState *state = (PluginState *)plugin->plugin_data; #ifdef __WIN32__ pgpl_window_thread_set_transient(state->get_window(), (void *)window->win32); #else pgpl_window_thread_set_transient(state->get_window(), (void *)&window->x11); #endif return true; }, [](const clap_plugin_t *plugin, const char *title) { PluginState *state = (PluginState *)plugin->plugin_data; pgpl_window_thread_set_title(state->get_window(), title); }, [](const clap_plugin_t *plugin) -> bool { PluginState *state = (PluginState *)plugin->plugin_data; pgpl_window_thread_show(state->get_window()); return true; }, [](const clap_plugin_t *plugin) -> bool { PluginState *state = (PluginState *)plugin->plugin_data; pgpl_window_thread_hide(state->get_window()); return true; }, }; static const clap_plugin_t plugin_class = { &plugin_descriptor, nullptr, [](const clap_plugin *) -> bool { return true; }, [](const clap_plugin *plugin) { delete (PluginState *)plugin->plugin_data; delete plugin; }, [](const clap_plugin *plugin, double sample_rate, uint32_t, uint32_t) -> bool { PluginState *state = (PluginState *)plugin->plugin_data; state->set_sample_rate(sample_rate); return true; }, [](const clap_plugin *) {}, [](const clap_plugin *) -> bool { return true; }, [](const clap_plugin *) {}, [](const clap_plugin *) {}, [](const clap_plugin *plugin, const clap_process_t *process) -> clap_process_status { PluginState *state = (PluginState *)plugin->plugin_data; uint32_t event_amount = process->in_events->size(process->in_events); uint32_t event_index = 0; uint32_t next_event_frame = event_amount ? 0 : process->frames_count; for (uint32_t i = 0; i < process->frames_count;) { while (event_index < event_amount && next_event_frame == i) { const clap_event_header_t *event = process->in_events->get(process->in_events, event_index); if (event->time != i) { next_event_frame = event->time; break; } state->process_event(event); event_index++; if (event_index == event_amount) { next_event_frame = process->frames_count; break; } } state->render_audio(process->audio_outputs[0].data32[0] + i, process->audio_outputs[0].data32[1] + i, next_event_frame - i); i = next_event_frame; } return CLAP_PROCESS_CONTINUE; }, [](const clap_plugin *, const char *id) -> const void * { if (0 == strcmp(id, CLAP_EXT_NOTE_PORTS)) return ¬e_ports; if (0 == strcmp(id, CLAP_EXT_AUDIO_PORTS)) return &audio_ports; if (0 == strcmp(id, CLAP_EXT_GUI)) return &gui; return nullptr; }, [](const clap_plugin *) {}, }; static const clap_plugin_factory_t plugin_factory = { [](const clap_plugin_factory *) -> uint32_t { return 1; }, [](const clap_plugin_factory *, uint32_t index) -> const clap_plugin_descriptor_t * { return index == 0 ? &plugin_descriptor : nullptr; }, [](const clap_plugin_factory *, const clap_host_t *host, const char *plugin_id) -> const clap_plugin_t * { if (!clap_version_is_compatible(host->clap_version) || strcmp(plugin_id, plugin_descriptor.id)) { return nullptr; } clap_plugin_t *plugin = new clap_plugin_t(plugin_class); plugin->plugin_data = new PluginState(); return plugin; }, }; extern "C" const clap_plugin_entry_t clap_entry = { CLAP_VERSION_INIT, [](const char *plugin_path) -> bool { pgpl_init(); srand(time(NULL)); font_path = new std::string(plugin_path + std::string(".ttf")); return true; }, []() { delete font_path; pgpl_deinit(); }, [](const char *factory_id) -> const void * { return strcmp(factory_id, CLAP_PLUGIN_FACTORY_ID) ? nullptr : &plugin_factory; }, };