aboutsummaryrefslogtreecommitdiffstats
path: root/plugins/eg-sampler.lv2/sampler_ui.c
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2016-10-04 00:46:56 -0400
committerDavid Robillard <d@drobilla.net>2016-10-04 00:47:20 -0400
commit472556ff28b99db416d6f0a926c32ea28698e1a3 (patch)
treee115542762a966d0303c8f226f5984d5fb378ac9 /plugins/eg-sampler.lv2/sampler_ui.c
parent2813b3f9f6a778641b3ff409a43e969cd8a8fc20 (diff)
downloadlv2-472556ff28b99db416d6f0a926c32ea28698e1a3.tar.xz
eg-sampler: Add waveform display to UI
Diffstat (limited to 'plugins/eg-sampler.lv2/sampler_ui.c')
-rw-r--r--plugins/eg-sampler.lv2/sampler_ui.c169
1 files changed, 128 insertions, 41 deletions
diff --git a/plugins/eg-sampler.lv2/sampler_ui.c b/plugins/eg-sampler.lv2/sampler_ui.c
index 23204c5..2373a4d 100644
--- a/plugins/eg-sampler.lv2/sampler_ui.c
+++ b/plugins/eg-sampler.lv2/sampler_ui.c
@@ -28,64 +28,136 @@
#include "lv2/lv2plug.in/ns/extensions/ui/ui.h"
#include "lv2/lv2plug.in/ns/lv2core/lv2_util.h"
-#include "./uris.h"
+#include "peaks.h"
+#include "uris.h"
#define SAMPLER_UI_URI "http://lv2plug.in/plugins/eg-sampler#ui"
+#define MIN_CANVAS_W 128
+#define MIN_CANVAS_H 80
+
typedef struct {
LV2_Atom_Forge forge;
LV2_URID_Map* map;
LV2_Log_Logger logger;
SamplerURIs uris;
+ PeaksReceiver precv;
LV2UI_Write_Function write;
LV2UI_Controller controller;
GtkWidget* box;
GtkWidget* button;
- GtkWidget* label;
+ GtkWidget* canvas;
GtkWidget* window; /* For optional show interface. */
+
+ uint32_t width;
+ uint32_t requested_n_peaks;
+ char* filename;
+
+ uint8_t forge_buf[1024];
} SamplerUI;
static void
-on_load_clicked(GtkWidget* widget,
- void* handle)
+on_file_set(GtkFileChooserButton* widget, void* handle)
{
SamplerUI* ui = (SamplerUI*)handle;
- /* Create a dialog to select a sample file. */
- GtkWidget* dialog = gtk_file_chooser_dialog_new(
- "Load Sample",
- NULL,
- GTK_FILE_CHOOSER_ACTION_OPEN,
- GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
- GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
- NULL);
+ // Get the filename from the file chooser
+ char* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
- /* Run the dialog, and return if it is cancelled. */
- if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
- gtk_widget_destroy(dialog);
- return;
- }
+ // Write a set message to the plugin to load new file
+ lv2_atom_forge_set_buffer(&ui->forge, ui->forge_buf, sizeof(ui->forge_buf));
+ LV2_Atom* msg = (LV2_Atom*)write_set_file(&ui->forge, &ui->uris,
+ filename, strlen(filename));
- /* Get the file path from the dialog. */
- char* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+ ui->write(ui->controller, 0, lv2_atom_total_size(msg),
+ ui->uris.atom_eventTransfer,
+ msg);
- /* Got what we need, destroy the dialog. */
- gtk_widget_destroy(dialog);
+ g_free(filename);
+}
-#define OBJ_BUF_SIZE 1024
- uint8_t obj_buf[OBJ_BUF_SIZE];
- lv2_atom_forge_set_buffer(&ui->forge, obj_buf, OBJ_BUF_SIZE);
+static void
+request_peaks(SamplerUI* ui, uint32_t n_peaks)
+{
+ lv2_atom_forge_set_buffer(&ui->forge, ui->forge_buf, sizeof(ui->forge_buf));
- LV2_Atom* msg = (LV2_Atom*)write_set_file(&ui->forge, &ui->uris,
- filename, strlen(filename));
+ LV2_Atom_Forge_Frame frame;
+ lv2_atom_forge_object(&ui->forge, &frame, 0, ui->uris.patch_Get);
+ lv2_atom_forge_key(&ui->forge, ui->uris.patch_accept);
+ lv2_atom_forge_urid(&ui->forge, ui->precv.uris.peaks_PeakUpdate);
+ lv2_atom_forge_key(&ui->forge, ui->precv.uris.peaks_total);
+ lv2_atom_forge_int(&ui->forge, n_peaks);
+ lv2_atom_forge_pop(&ui->forge, &frame);
+ LV2_Atom* msg = lv2_atom_forge_deref(&ui->forge, frame.ref);
ui->write(ui->controller, 0, lv2_atom_total_size(msg),
ui->uris.atom_eventTransfer,
msg);
- g_free(filename);
+ ui->requested_n_peaks = n_peaks;
+}
+
+/** Set Cairo color to a GDK color (to follow Gtk theme). */
+static void
+cairo_set_source_gdk(cairo_t* cr, const GdkColor* color)
+{
+ cairo_set_source_rgb(
+ cr, color->red / 65535.0, color->green / 65535.0, color->blue / 65535.0);
+
+}
+
+static gboolean
+on_canvas_expose(GtkWidget* widget, GdkEventExpose* event, gpointer data)
+{
+ SamplerUI* ui = (SamplerUI*)data;
+
+ GtkAllocation size;
+ gtk_widget_get_allocation(widget, &size);
+
+ ui->width = size.width;
+ if ((uint32_t)ui->width > 2 * ui->requested_n_peaks) {
+ request_peaks(ui, 2 * ui->requested_n_peaks);
+ }
+
+ cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(widget));
+
+ const int mid_y = size.height / 2;
+
+ const float* const peaks = ui->precv.peaks;
+ const int32_t n_peaks = ui->precv.n_peaks;
+ if (peaks) {
+ // Draw waveform
+ const double scale = size.width / ((double)n_peaks - 1.0f);
+
+ // Start at left origin
+ cairo_move_to(cr, 0, mid_y);
+
+ // Draw line through top peaks
+ for (int i = 0; i < n_peaks; ++i) {
+ const float peak = peaks[i];
+ cairo_line_to(cr, i * scale, mid_y + (peak / 2.0f) * size.height);
+ }
+
+ // Continue through bottom peaks
+ for (int i = n_peaks - 1; i >= 0; --i) {
+ const float peak = peaks[i];
+ cairo_line_to(cr, i * scale, mid_y - (peak / 2.0f) * size.height);
+ }
+
+ // Close shape
+ cairo_line_to(cr, 0, mid_y);
+
+ cairo_set_source_gdk(cr, widget->style->mid);
+ cairo_fill_preserve(cr);
+
+ cairo_set_source_gdk(cr, widget->style->fg);
+ cairo_stroke(cr);
+ }
+
+ cairo_destroy(cr);
+ return TRUE;
}
static LV2UI_Handle
@@ -104,6 +176,7 @@ instantiate(const LV2UI_Descriptor* descriptor,
ui->write = write_function;
ui->controller = controller;
+ ui->width = MIN_CANVAS_W;
*widget = NULL;
// Get host features
@@ -122,21 +195,25 @@ instantiate(const LV2UI_Descriptor* descriptor,
// Map URIs and initialise forge
map_sampler_uris(ui->map, &ui->uris);
lv2_atom_forge_init(&ui->forge, ui->map);
+ peaks_receiver_init(&ui->precv, ui->map);
// Construct Gtk UI
ui->box = gtk_vbox_new(FALSE, 4);
- ui->label = gtk_label_new("?");
- ui->button = gtk_button_new_with_label("Load Sample");
- gtk_box_pack_start(GTK_BOX(ui->box), ui->label, TRUE, TRUE, 4);
- gtk_box_pack_start(GTK_BOX(ui->box), ui->button, FALSE, FALSE, 4);
- g_signal_connect(ui->button, "clicked",
- G_CALLBACK(on_load_clicked),
+ ui->button = gtk_file_chooser_button_new("Load Sample", GTK_FILE_CHOOSER_ACTION_OPEN);
+ ui->canvas = gtk_drawing_area_new();
+ gtk_widget_set_size_request(ui->canvas, MIN_CANVAS_W, MIN_CANVAS_H);
+ gtk_container_set_border_width(GTK_CONTAINER(ui->box), 4);
+ gtk_box_pack_start(GTK_BOX(ui->box), ui->canvas, TRUE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(ui->box), ui->button, FALSE, TRUE, 0);
+ g_signal_connect(ui->button, "file-set",
+ G_CALLBACK(on_file_set),
+ ui);
+ g_signal_connect(G_OBJECT(ui->canvas), "expose_event",
+ G_CALLBACK(on_canvas_expose),
ui);
// Request state (filename) from plugin
- uint8_t get_buf[512];
- lv2_atom_forge_set_buffer(&ui->forge, get_buf, sizeof(get_buf));
-
+ lv2_atom_forge_set_buffer(&ui->forge, ui->forge_buf, sizeof(ui->forge_buf));
LV2_Atom_Forge_Frame frame;
LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object(
&ui->forge, &frame, 0, ui->uris.patch_Get);
@@ -171,11 +248,21 @@ port_event(LV2UI_Handle handle,
const LV2_Atom* atom = (const LV2_Atom*)buffer;
if (lv2_atom_forge_is_object_type(&ui->forge, atom->type)) {
const LV2_Atom_Object* obj = (const LV2_Atom_Object*)atom;
- const char* uri = read_set_file(&ui->uris, obj);
- if (uri) {
- gtk_label_set_text(GTK_LABEL(ui->label), uri);
- } else {
- lv2_log_warning(&ui->logger, "Malformed message\n");
+ if (obj->body.otype == ui->uris.patch_Set) {
+ const char* path = read_set_file(&ui->uris, obj);
+ if (path && (!ui->filename || strcmp(path, ui->filename))) {
+ g_free(ui->filename);
+ ui->filename = g_strdup(path);
+ gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(ui->button), path);
+ peaks_receiver_clear(&ui->precv);
+ request_peaks(ui, ui->width / 2 * 2);
+ } else if (!path) {
+ lv2_log_warning(&ui->logger, "Set message has no path\n");
+ }
+ } else if (obj->body.otype == ui->precv.uris.peaks_PeakUpdate) {
+ if (!peaks_receiver_receive(&ui->precv, obj)) {
+ gtk_widget_queue_draw(ui->canvas);
+ }
}
} else {
lv2_log_error(&ui->logger, "Unknown message type\n");