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 	          &note_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