426 lines
13 KiB
C++
426 lines
13 KiB
C++
#include "clap/clap.h"
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <pgpl.h>
|
|
#include <stdio.h>
|
|
#include <string>
|
|
#include <time.h>
|
|
#include <vector>
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
counter_button =
|
|
pgpl_gui_button_widget_create("Click me!", on_counter_button_click);
|
|
|
|
counter = pgpl_gui_text_widget_create(counter_buffer);
|
|
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<Note> 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;
|
|
},
|
|
};
|