1 /*
2 LV2 Sampler Example Plugin UI
3 Copyright 2011-2016 David Robillard <d@drobilla.net>
4
5 Permission to use, copy, modify, and/or distribute this software for any
6 purpose with or without fee is hereby granted, provided that the above
7 copyright notice and this permission notice appear in all copies.
8
9 THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18 #include "peaks.h"
19 #include "uris.h"
20
21 #include "lv2/atom/atom.h"
22 #include "lv2/atom/forge.h"
23 #include "lv2/atom/util.h"
24 #include "lv2/core/lv2.h"
25 #include "lv2/core/lv2_util.h"
26 #include "lv2/log/log.h"
27 #include "lv2/log/logger.h"
28 #include "lv2/midi/midi.h"
29 #include "lv2/ui/ui.h"
30 #include "lv2/urid/urid.h"
31
32 #include <cairo.h>
33 #include <gdk/gdk.h>
34 #include <glib-object.h>
35 #include <glib.h>
36 #include <gobject/gclosure.h>
37 #include <gtk/gtk.h>
38
39 #include <stdbool.h>
40 #include <stdint.h>
41 #include <stdlib.h>
42 #include <string.h>
43
44 #define SAMPLER_UI_URI "http://lv2plug.in/plugins/eg-sampler#ui"
45
46 #define MIN_CANVAS_W 128
47 #define MIN_CANVAS_H 80
48
49 typedef struct {
50 LV2_Atom_Forge forge;
51 LV2_URID_Map* map;
52 LV2_Log_Logger logger;
53 SamplerURIs uris;
54 PeaksReceiver precv;
55
56 LV2UI_Write_Function write;
57 LV2UI_Controller controller;
58
59 GtkWidget* box;
60 GtkWidget* play_button;
61 GtkWidget* file_button;
62 GtkWidget* button_box;
63 GtkWidget* canvas;
64 GtkWidget* window; /* For optional show interface. */
65
66 uint32_t width;
67 uint32_t requested_n_peaks;
68 char* filename;
69
70 uint8_t forge_buf[1024];
71 } SamplerUI;
72
73 static void
on_file_set(GtkFileChooserButton * widget,void * handle)74 on_file_set(GtkFileChooserButton* widget, void* handle)
75 {
76 SamplerUI* ui = (SamplerUI*)handle;
77
78 // Get the filename from the file chooser
79 char* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
80
81 // Write a set message to the plugin to load new file
82 lv2_atom_forge_set_buffer(&ui->forge, ui->forge_buf, sizeof(ui->forge_buf));
83 LV2_Atom* msg = (LV2_Atom*)write_set_file(&ui->forge, &ui->uris,
84 filename, strlen(filename));
85
86 ui->write(ui->controller, 0, lv2_atom_total_size(msg),
87 ui->uris.atom_eventTransfer,
88 msg);
89
90 g_free(filename);
91 }
92
93 static void
on_play_clicked(GtkFileChooserButton * widget,void * handle)94 on_play_clicked(GtkFileChooserButton* widget, void* handle)
95 {
96 SamplerUI* ui = (SamplerUI*)handle;
97 struct {
98 LV2_Atom atom;
99 uint8_t msg[3];
100 } note_on;
101
102 note_on.atom.type = ui->uris.midi_Event;
103 note_on.atom.size = 3;
104 note_on.msg[0] = LV2_MIDI_MSG_NOTE_ON;
105 note_on.msg[1] = 60;
106 note_on.msg[2] = 60;
107 ui->write(ui->controller, 0, sizeof(note_on),
108 ui->uris.atom_eventTransfer,
109 ¬e_on);
110 }
111
112 static void
request_peaks(SamplerUI * ui,uint32_t n_peaks)113 request_peaks(SamplerUI* ui, uint32_t n_peaks)
114 {
115 if (n_peaks == ui->requested_n_peaks) {
116 return;
117 }
118
119 lv2_atom_forge_set_buffer(&ui->forge, ui->forge_buf, sizeof(ui->forge_buf));
120
121 LV2_Atom_Forge_Frame frame;
122 lv2_atom_forge_object(&ui->forge, &frame, 0, ui->uris.patch_Get);
123 lv2_atom_forge_key(&ui->forge, ui->uris.patch_accept);
124 lv2_atom_forge_urid(&ui->forge, ui->precv.uris.peaks_PeakUpdate);
125 lv2_atom_forge_key(&ui->forge, ui->precv.uris.peaks_total);
126 lv2_atom_forge_int(&ui->forge, n_peaks);
127 lv2_atom_forge_pop(&ui->forge, &frame);
128
129 LV2_Atom* msg = lv2_atom_forge_deref(&ui->forge, frame.ref);
130 ui->write(ui->controller, 0, lv2_atom_total_size(msg),
131 ui->uris.atom_eventTransfer,
132 msg);
133
134 ui->requested_n_peaks = n_peaks;
135 }
136
137 /** Set Cairo color to a GDK color (to follow Gtk theme). */
138 static void
cairo_set_source_gdk(cairo_t * cr,const GdkColor * color)139 cairo_set_source_gdk(cairo_t* cr, const GdkColor* color)
140 {
141 cairo_set_source_rgb(
142 cr, color->red / 65535.0, color->green / 65535.0, color->blue / 65535.0);
143
144 }
145
146 static gboolean
on_canvas_expose(GtkWidget * widget,GdkEventExpose * event,gpointer data)147 on_canvas_expose(GtkWidget* widget, GdkEventExpose* event, gpointer data)
148 {
149 SamplerUI* ui = (SamplerUI*)data;
150
151 GtkAllocation size;
152 gtk_widget_get_allocation(widget, &size);
153
154 ui->width = size.width;
155 if ((uint32_t)ui->width > 2 * ui->requested_n_peaks) {
156 request_peaks(ui, 2 * ui->requested_n_peaks);
157 }
158
159 cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(widget));
160
161 cairo_set_line_width(cr, 1.0);
162 cairo_translate(cr, 0.5, 0.5);
163
164 const int mid_y = size.height / 2;
165
166 const float* const peaks = ui->precv.peaks;
167 const int32_t n_peaks = ui->precv.n_peaks;
168 if (peaks) {
169 // Draw waveform
170 const double scale = size.width / ((double)n_peaks - 1.0f);
171
172 // Start at left origin
173 cairo_move_to(cr, 0, mid_y);
174
175 // Draw line through top peaks
176 for (int i = 0; i < n_peaks; ++i) {
177 const float peak = peaks[i];
178 cairo_line_to(cr, i * scale, mid_y + (peak / 2.0f) * size.height);
179 }
180
181 // Continue through bottom peaks
182 for (int i = n_peaks - 1; i >= 0; --i) {
183 const float peak = peaks[i];
184 cairo_line_to(cr, i * scale, mid_y - (peak / 2.0f) * size.height);
185 }
186
187 // Close shape
188 cairo_line_to(cr, 0, mid_y);
189
190 cairo_set_source_gdk(cr, widget->style->mid);
191 cairo_fill_preserve(cr);
192
193 cairo_set_source_gdk(cr, widget->style->fg);
194 cairo_stroke(cr);
195 }
196
197 cairo_destroy(cr);
198 return TRUE;
199 }
200
201 static LV2UI_Handle
instantiate(const LV2UI_Descriptor * descriptor,const char * plugin_uri,const char * bundle_path,LV2UI_Write_Function write_function,LV2UI_Controller controller,LV2UI_Widget * widget,const LV2_Feature * const * features)202 instantiate(const LV2UI_Descriptor* descriptor,
203 const char* plugin_uri,
204 const char* bundle_path,
205 LV2UI_Write_Function write_function,
206 LV2UI_Controller controller,
207 LV2UI_Widget* widget,
208 const LV2_Feature* const* features)
209 {
210 SamplerUI* ui = (SamplerUI*)calloc(1, sizeof(SamplerUI));
211 if (!ui) {
212 return NULL;
213 }
214
215 ui->write = write_function;
216 ui->controller = controller;
217 ui->width = MIN_CANVAS_W;
218 *widget = NULL;
219
220 // Get host features
221 const char* missing = lv2_features_query(
222 features,
223 LV2_LOG__log, &ui->logger.log, false,
224 LV2_URID__map, &ui->map, true,
225 NULL);
226 lv2_log_logger_set_map(&ui->logger, ui->map);
227 if (missing) {
228 lv2_log_error(&ui->logger, "Missing feature <%s>\n", missing);
229 free(ui);
230 return NULL;
231 }
232
233 // Map URIs and initialise forge
234 map_sampler_uris(ui->map, &ui->uris);
235 lv2_atom_forge_init(&ui->forge, ui->map);
236 peaks_receiver_init(&ui->precv, ui->map);
237
238 // Construct Gtk UI
239 ui->box = gtk_vbox_new(FALSE, 4);
240 ui->play_button = gtk_button_new_with_label("▶");
241 ui->canvas = gtk_drawing_area_new();
242 ui->button_box = gtk_hbox_new(FALSE, 4);
243 ui->file_button = gtk_file_chooser_button_new(
244 "Load Sample", GTK_FILE_CHOOSER_ACTION_OPEN);
245 gtk_widget_set_size_request(ui->canvas, MIN_CANVAS_W, MIN_CANVAS_H);
246 gtk_container_set_border_width(GTK_CONTAINER(ui->box), 4);
247 gtk_box_pack_start(GTK_BOX(ui->box), ui->canvas, TRUE, TRUE, 0);
248 gtk_box_pack_start(GTK_BOX(ui->box), ui->button_box, FALSE, TRUE, 0);
249 gtk_box_pack_start(GTK_BOX(ui->button_box), ui->play_button, FALSE, FALSE, 0);
250 gtk_box_pack_start(GTK_BOX(ui->button_box), ui->file_button, TRUE, TRUE, 0);
251
252 g_signal_connect(ui->file_button, "file-set",
253 G_CALLBACK(on_file_set), ui);
254
255 g_signal_connect(ui->play_button, "clicked",
256 G_CALLBACK(on_play_clicked), ui);
257
258 g_signal_connect(G_OBJECT(ui->canvas), "expose_event",
259 G_CALLBACK(on_canvas_expose), ui);
260
261 // Request state (filename) from plugin
262 lv2_atom_forge_set_buffer(&ui->forge, ui->forge_buf, sizeof(ui->forge_buf));
263 LV2_Atom_Forge_Frame frame;
264 LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object(
265 &ui->forge, &frame, 0, ui->uris.patch_Get);
266 lv2_atom_forge_pop(&ui->forge, &frame);
267
268 ui->write(ui->controller, 0, lv2_atom_total_size(msg),
269 ui->uris.atom_eventTransfer,
270 msg);
271
272 *widget = ui->box;
273
274 return ui;
275 }
276
277 static void
cleanup(LV2UI_Handle handle)278 cleanup(LV2UI_Handle handle)
279 {
280 SamplerUI* ui = (SamplerUI*)handle;
281 gtk_widget_destroy(ui->box);
282 gtk_widget_destroy(ui->play_button);
283 gtk_widget_destroy(ui->canvas);
284 gtk_widget_destroy(ui->button_box);
285 gtk_widget_destroy(ui->file_button);
286 free(ui);
287 }
288
289 static void
port_event(LV2UI_Handle handle,uint32_t port_index,uint32_t buffer_size,uint32_t format,const void * buffer)290 port_event(LV2UI_Handle handle,
291 uint32_t port_index,
292 uint32_t buffer_size,
293 uint32_t format,
294 const void* buffer)
295 {
296 SamplerUI* ui = (SamplerUI*)handle;
297 if (format == ui->uris.atom_eventTransfer) {
298 const LV2_Atom* atom = (const LV2_Atom*)buffer;
299 if (lv2_atom_forge_is_object_type(&ui->forge, atom->type)) {
300 const LV2_Atom_Object* obj = (const LV2_Atom_Object*)atom;
301 if (obj->body.otype == ui->uris.patch_Set) {
302 const char* path = read_set_file(&ui->uris, obj);
303 if (path && (!ui->filename || strcmp(path, ui->filename))) {
304 g_free(ui->filename);
305 ui->filename = g_strdup(path);
306 gtk_file_chooser_set_filename(
307 GTK_FILE_CHOOSER(ui->file_button), path);
308 peaks_receiver_clear(&ui->precv);
309 ui->requested_n_peaks = 0;
310 request_peaks(ui, ui->width / 2 * 2);
311 } else if (!path) {
312 lv2_log_warning(&ui->logger, "Set message has no path\n");
313 }
314 } else if (obj->body.otype == ui->precv.uris.peaks_PeakUpdate) {
315 if (!peaks_receiver_receive(&ui->precv, obj)) {
316 gtk_widget_queue_draw(ui->canvas);
317 }
318 }
319 } else {
320 lv2_log_error(&ui->logger, "Unknown message type\n");
321 }
322 } else {
323 lv2_log_warning(&ui->logger, "Unknown port event format\n");
324 }
325 }
326
327 /* Optional non-embedded UI show interface. */
328 static int
ui_show(LV2UI_Handle handle)329 ui_show(LV2UI_Handle handle)
330 {
331 SamplerUI* ui = (SamplerUI*)handle;
332
333 int argc = 0;
334 gtk_init(&argc, NULL);
335
336 ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
337 gtk_container_add(GTK_CONTAINER(ui->window), ui->box);
338 gtk_widget_show_all(ui->window);
339 gtk_window_present(GTK_WINDOW(ui->window));
340
341 return 0;
342 }
343
344 /* Optional non-embedded UI hide interface. */
345 static int
ui_hide(LV2UI_Handle handle)346 ui_hide(LV2UI_Handle handle)
347 {
348 return 0;
349 }
350
351 /* Idle interface for optional non-embedded UI. */
352 static int
ui_idle(LV2UI_Handle handle)353 ui_idle(LV2UI_Handle handle)
354 {
355 SamplerUI* ui = (SamplerUI*)handle;
356 if (ui->window) {
357 gtk_main_iteration();
358 }
359 return 0;
360 }
361
362 static const void*
extension_data(const char * uri)363 extension_data(const char* uri)
364 {
365 static const LV2UI_Show_Interface show = { ui_show, ui_hide };
366 static const LV2UI_Idle_Interface idle = { ui_idle };
367 if (!strcmp(uri, LV2_UI__showInterface)) {
368 return &show;
369 } else if (!strcmp(uri, LV2_UI__idleInterface)) {
370 return &idle;
371 }
372 return NULL;
373 }
374
375 static const LV2UI_Descriptor descriptor = {
376 SAMPLER_UI_URI,
377 instantiate,
378 cleanup,
379 port_event,
380 extension_data
381 };
382
383 LV2_SYMBOL_EXPORT
384 const LV2UI_Descriptor*
lv2ui_descriptor(uint32_t index)385 lv2ui_descriptor(uint32_t index)
386 {
387 switch (index) {
388 case 0:
389 return &descriptor;
390 default:
391 return NULL;
392 }
393 }
394