1 /*
2 * Copyright (C) 2018-2020 Oleg Kapitonov
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 * --------------------------------------------------------------------------
18 */
19
20 #include <limits.h>
21 #include <math.h>
22 #include <stdint.h>
23 #include <string.h>
24
25 #include "lv2/lv2plug.in/ns/lv2core/lv2.h"
26 #include "lv2/lv2plug.in/ns/extensions/ui/ui.h"
27
28 #include <stdio.h>
29 #include <stdlib.h>
30
31 #include <cairo.h>
32 #include <cairo-xcb.h>
33
34 #include <xcb/xcb.h>
35 #include <xcb/xcb_aux.h>
36 #include <xcb/xcb_icccm.h>
37
38 // Port numbers for communication with the main LV2 plugin module
39 enum {PORT_BYPASS, PORT_BASS, PORT_DRIVE, PORT_MIDDLE, PORT_TREBLE, PORT_VOICE, PORT_LEVEL};
40
41 // Dial GUI element structure
42 typedef struct
43 {
44 int value; // 0 - 100
45 int start_value; // Holds value at the start of dialing
46 int base_x; // Coordinates of Dial in main window
47 int base_y; //
48 } st_dial;
49
50 // Point
51 typedef struct
52 {
53 int x;
54 int y;
55 }st_point;
56
57 // Color
58 typedef struct
59 {
60 double r;
61 double g;
62 double b;
63 }st_rgb;
64
65 // GUI window structure
66 typedef struct
67 {
68 xcb_connection_t *connection;
69
70 xcb_window_t win;
71
72 int width, height;
73 long event_mask;
74
75 void *controller;
76
77 LV2UI_Write_Function write_function;
78 LV2UI_Resize* resize;
79
80
81 // Main window
82 //win_t win;
83
84 // Dials
85 st_dial driveDial;
86 st_dial bassDial;
87 st_dial middleDial;
88 st_dial trebleDial;
89 st_dial voiceDial;
90 st_dial volumeDial;
91
92 // Holds state of bypass button
93 int bypass_flag;
94
95 // Hold mouse coordinates at the start of dialing
96 int pos_x;
97 int pos_y;
98
99 // Cairo objects
100 cairo_t *cr;
101 cairo_surface_t *surface;
102 xcb_visualtype_t *visual;
103
104 cairo_surface_t *image,*image2;
105 cairo_device_t *device;
106
107 // Port number (from enum) of the Dial, which is now
108 // adjusted by the user
109 // -1 means that no dial is adjusted
110 int active_dial;
111
112 } win_t;
113
114 // Create main window
win_init(win_t * win,xcb_screen_t * screen,xcb_window_t parentXwindow)115 static void win_init(win_t *win, xcb_screen_t *screen,
116 xcb_window_t parentXwindow)
117 {
118 win->width = 442;
119 win->height = 600;
120
121 win->win = xcb_generate_id(win->connection);
122
123 uint32_t mask = XCB_CW_EVENT_MASK;
124 uint32_t mask_values[1] = {
125 XCB_EVENT_MASK_STRUCTURE_NOTIFY |
126 XCB_EVENT_MASK_EXPOSURE |
127 XCB_EVENT_MASK_BUTTON_PRESS |
128 XCB_EVENT_MASK_BUTTON_1_MOTION };
129
130 xcb_create_window(win->connection, XCB_COPY_FROM_PARENT,
131 win->win, parentXwindow,
132 0, 0, win->width, win->height, 0, XCB_WINDOW_CLASS_COPY_FROM_PARENT,
133 XCB_COPY_FROM_PARENT, mask, mask_values);
134
135 xcb_size_hints_t size_hints;
136 memset(&size_hints, 0, sizeof(size_hints));
137 xcb_icccm_size_hints_set_size(&size_hints, 1, win->width, win->height);
138 xcb_icccm_size_hints_set_min_size(&size_hints, win->width, win->height);
139 xcb_icccm_size_hints_set_max_size(&size_hints, win->width, win->height);
140
141 xcb_icccm_set_wm_normal_hints(win->connection, win->win, &size_hints);
142
143 xcb_map_window(win->connection, win->win);
144 xcb_flush(win->connection);
145 }
146
147 // LV2 initialization function
148 static LV2UI_Handle
instantiate(const struct _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)149 instantiate(const struct _LV2UI_Descriptor * descriptor,
150 const char * plugin_uri,
151 const char * bundle_path,
152 LV2UI_Write_Function write_function,
153 LV2UI_Controller controller,
154 LV2UI_Widget * widget,
155 const LV2_Feature * const * features)
156 {
157 // Quit if GUI module loaded with wrong main module
158 if (strcmp(plugin_uri, PLUGIN_URI))
159 {
160 fprintf(stderr,
161 "%s: ERROR: this GUI does not support plugin with URI %s\n",
162 PLUGIN_URI, plugin_uri);
163 return NULL;
164 }
165
166 win_t *win = (win_t *)malloc(sizeof(win_t));
167
168 win->active_dial = -1;
169
170 win->driveDial.value = 0;
171 win->driveDial.start_value = 0;
172 win->driveDial.base_x = 62;
173 win->driveDial.base_y = 86;
174
175 win->bassDial.value = 0;
176 win->bassDial.start_value = 0;
177 win->bassDial.base_x = 21;
178 win->bassDial.base_y = 450;
179
180 win->middleDial.value = 0;
181 win->middleDial.start_value = 0;
182 win->middleDial.base_x = 57;
183 win->middleDial.base_y = 304;
184
185 win->trebleDial.value = 0;
186 win->trebleDial.start_value = 0;
187 win->trebleDial.base_x = 174;
188 win->trebleDial.base_y = 220;
189
190 win->voiceDial.value = 0;
191 win->voiceDial.start_value = 0;
192 win->voiceDial.base_x = 314;
193 win->voiceDial.base_y = 215;
194
195 win->volumeDial.value = 0;
196 win->volumeDial.start_value = 0;
197 win->volumeDial.base_x = 247;
198 win->volumeDial.base_y = 68;
199
200 // X11 handle of host window, in which plugin GUI
201 // window must be embedded
202 void * parentXwindow = 0;
203
204 // Resize function provided by LV2 host
205 LV2UI_Resize* resize = NULL;
206
207 // Obtain this pointers
208 for (int i = 0; features[i]; ++i)
209 {
210 if (!strcmp(features[i]->URI, LV2_UI__parent))
211 {
212 parentXwindow = features[i]->data;
213 }
214 else if (!strcmp(features[i]->URI, LV2_UI__resize))
215 {
216 resize = (LV2UI_Resize*)features[i]->data;
217 }
218 }
219
220 win->connection = xcb_connect(NULL, NULL);
221
222 if (win->connection == NULL)
223 {
224 fprintf(stderr, "Failed to open display\n");
225 return NULL;
226 }
227
228 xcb_screen_t *screen =
229 xcb_setup_roots_iterator(xcb_get_setup(win->connection)).data;
230
231 win_init(win, screen, (xcb_window_t) (size_t) parentXwindow);
232
233 win->visual = xcb_aux_find_visual_by_id(screen, screen->root_visual);
234 xcb_clear_area(win->connection, 0, win->win, 0, 0, 0, 0);
235 win->surface = cairo_xcb_surface_create(win->connection, win->win, win->visual,
236 win->width, win->height);
237
238 win->device = cairo_device_reference(cairo_surface_get_device(win->surface));
239
240 win->cr = cairo_create(win->surface);
241
242 char image_path[PATH_MAX];
243
244 // Load background image
245 snprintf(image_path, sizeof(image_path), "%s/base_scale.png", bundle_path);
246 image_path[sizeof(image_path) - 1] = '\0';
247 win->image = cairo_image_surface_create_from_png (image_path);
248
249 // Load dial point image
250 snprintf(image_path, sizeof(image_path), "%s/light.png", bundle_path);
251 image_path[sizeof(image_path) - 1] = '\0';
252 win->image2 = cairo_image_surface_create_from_png (image_path);
253
254 // Return to host the X11 handle of created window
255 *widget = (void*) (size_t) win->win;
256
257 if (resize)
258 {
259 win->resize = resize;
260 // Ask the host to properly resize the plugin host window
261 resize->ui_resize(resize->handle, win->width, win->height);
262 }
263
264 win->controller = controller;
265 win->write_function = write_function;
266
267 return (LV2UI_Handle)win;
268 }
269
win_deinit(win_t * win)270 static void win_deinit(win_t *win)
271 {
272 xcb_destroy_window(win->connection, win->win);
273 }
274
275 static void
cleanup(LV2UI_Handle ui)276 cleanup(LV2UI_Handle ui)
277 {
278 win_t *win = (win_t *)ui;
279 win_deinit(win);
280 cairo_destroy(win->cr);
281 cairo_surface_destroy(win->surface);
282 cairo_surface_destroy(win->image);
283 cairo_surface_destroy(win->image2);
284 cairo_device_finish(win->device);
285 cairo_device_destroy(win->device);
286 xcb_disconnect(win->connection);
287 free(win);
288 }
289
290 // Limit value by range 0 - 100
clamp(int value)291 int clamp(int value)
292 {
293 if (value < 0) return 0;
294 if (value > 100) return 100;
295 return value;
296 }
297
298 // Convert a value in dB to the linear range 0 - 100
299 // 0 corresponds to `-range` dB, 100 corresponds to `range` dB
db_to_value(float db,float range)300 int db_to_value(float db, float range)
301 {
302 int value = clamp((int)((db + range)/2.0/range*100.0));
303 return value;
304 }
305
306 // Calculate RGB color of Dial's LED for given value
value_to_color(int value)307 st_rgb value_to_color(int value)
308 {
309 st_rgb color;
310 color.r = 0.01*pow(value,1.0/3.0)*100.0/pow(100,1.0/3.0);
311 color.g = 0.7874*(1.0-pow(value,3)/1000000.0);
312 color.b = 0;
313 return color;
314 }
315
316 // Calculate coordinates of Dial's LED for given value
value_to_xy(int value)317 st_point value_to_xy(int value)
318 {
319 int pointerAngle = value/100.0*(360.0-80.0-80.0) - 105.0;
320 st_point p;
321 p.x = 43 + 27*sin(pointerAngle/180.0*M_PI);
322 p.y = 43 - 27*cos(pointerAngle/180.0*M_PI);
323 return p;
324 }
325
draw_dial(cairo_t * cr,cairo_surface_t * image,int value,int base_x,int base_y)326 void draw_dial(cairo_t *cr, cairo_surface_t *image, int value, int base_x, int base_y)
327 {
328 st_rgb color = value_to_color(value);
329 st_point p = value_to_xy(value);
330
331 cairo_set_source_rgb(cr, color.r, color.g, color.b);
332 cairo_arc(cr, base_x+p.x+10.5, base_y+p.y+10, 5.5, 0, 2 * M_PI);
333 cairo_fill(cr);
334 cairo_set_source_surface (cr, image, base_x+p.x, base_y+p.y);
335 cairo_paint (cr);
336 }
337
win_draw(win_t * win)338 static void win_draw(win_t *win)
339 {
340 cairo_push_group (win->cr);
341
342 if (win->bypass_flag == 0)
343 {
344 cairo_set_source_rgb(win->cr, 1.0, 0.63, 0.0);
345 cairo_arc(win->cr, 213, 428, 12, 0, 2 * M_PI);
346 cairo_fill(win->cr);
347 }
348 else
349 {
350 cairo_set_source_rgb(win->cr, 0.0, 0.0, 0.0);
351 cairo_arc(win->cr, 213, 428, 12, 0, 2 * M_PI);
352 cairo_fill(win->cr);
353 }
354
355
356 cairo_set_source_surface (win->cr, win->image, 0, 0);
357 cairo_paint (win->cr);
358
359 draw_dial(win->cr,
360 win->image2,
361 win->driveDial.value,
362 win->driveDial.base_x,
363 win->driveDial.base_y);
364 draw_dial(win->cr,
365 win->image2,
366 win->bassDial.value,
367 win->bassDial.base_x,
368 win->bassDial.base_y);
369 draw_dial(win->cr,
370 win->image2,
371 win->middleDial.value,
372 win->middleDial.base_x,
373 win->middleDial.base_y);
374 draw_dial(win->cr,
375 win->image2,
376 win->trebleDial.value,
377 win->trebleDial.base_x,
378 win->trebleDial.base_y);
379 draw_dial(win->cr,
380 win->image2,
381 win->voiceDial.value,
382 win->voiceDial.base_x,
383 win->voiceDial.base_y);
384 draw_dial(win->cr,
385 win->image2,
386 win->volumeDial.value,
387 win->volumeDial.base_x,
388 win->volumeDial.base_y);
389
390 cairo_pop_group_to_source (win->cr);
391 cairo_paint (win->cr);
392 }
393
394 // Callback from host to change Dial values
port_event(LV2UI_Handle ui,uint32_t port_index,uint32_t buffer_size,uint32_t format,const void * buffer)395 static void port_event(LV2UI_Handle ui,
396 uint32_t port_index,
397 uint32_t buffer_size,
398 uint32_t format,
399 const void * buffer)
400 {
401 win_t *win = (win_t *)ui;
402 switch (port_index)
403 {
404 case PORT_BYPASS:
405 {
406 float bypass_value = *(float*)buffer;
407 if (bypass_value > 0.5) win->bypass_flag = 1;
408 else win->bypass_flag = 0;
409 }
410 break;
411 case PORT_BASS:
412 win->bassDial.value = db_to_value(*(float*)buffer, 15.0);
413 break;
414 case PORT_DRIVE:
415 win->driveDial.value = clamp((int)(*(float*)buffer));
416 break;
417 case PORT_LEVEL:
418 win->volumeDial.value = clamp((int)((*(float*)buffer)*100.0));
419 break;
420 case PORT_MIDDLE:
421 win->middleDial.value = db_to_value(*(float*)buffer, 15.0);
422 break;
423 case PORT_TREBLE:
424 win->trebleDial.value = db_to_value(*(float*)buffer, 15.0);
425 break;
426 case PORT_VOICE:
427 win->voiceDial.value = clamp((int)((*(float*)buffer)*100.0));
428 }
429 // Redraw window due to value change
430 win_draw(win);
431 }
432
433 // Determine if the point with given coordinates
434 // belongs to the given Dial
is_point_in_dial_area(int x,int y,st_dial * dial)435 bool is_point_in_dial_area(int x, int y, st_dial *dial)
436 {
437 if ((x >= dial->base_x + 5) && (x <= dial->base_x + 100)
438 && (y >= dial->base_y + 5) && (y <= dial->base_y + 105))
439 {
440 return true;
441 }
442 return false;
443 }
444
445 // Determine if the point with given coordinates
446 // belongs to the given area
is_point_in_area(int x,int y,int topleft_x,int topleft_y,int bottomright_x,int bottomright_y)447 bool is_point_in_area(int x, int y, int topleft_x, int topleft_y,
448 int bottomright_x, int bottomright_y)
449 {
450 if ((x >= topleft_x) && (x <= bottomright_x)
451 && (y >= topleft_y) && (y <= bottomright_y))
452 {
453 return true;
454 }
455 return false;
456 }
457
458 // Convert the linear value 0 - 100 to the dB value,
459 // 0 corresponds to `-range` dB, 100 corresponds to `range` dB
value_to_db(int value,float range)460 float value_to_db(int value, float range)
461 {
462 float db = value / 100.0 * 2.0 * range - range;
463 return db;
464 }
465
466 // Process X11 events
467 static void
win_handle_events(win_t * win)468 win_handle_events(win_t *win)
469 {
470 xcb_flush(win->connection);
471 xcb_generic_event_t *event;
472 while ((event = xcb_poll_for_event(win->connection)) != NULL)
473 {
474 switch(event->response_type & ~0x80)
475 {
476 case XCB_EXPOSE: // the window must be redrawn
477 {
478 xcb_expose_event_t *eev = (xcb_expose_event_t *) event;
479
480 if (eev->count == 0)
481 {
482 win_draw(win);
483 }
484 }
485 break;
486 case XCB_BUTTON_PRESS: // Left mouse button pressed
487 {
488 xcb_button_press_event_t *bev = (xcb_button_press_event_t *) event;
489 if (bev->detail == XCB_BUTTON_INDEX_1)
490 {
491 // Save position
492 win->pos_x = bev->event_x;
493 win->pos_y = bev->event_y;
494
495 // Find on which dial the button was pressed
496 if (is_point_in_dial_area(win->pos_x, win->pos_y, &(win->driveDial)))
497 {
498 win->driveDial.start_value = win->driveDial.value;
499 win->active_dial = PORT_DRIVE;
500 }
501 else if (is_point_in_dial_area(win->pos_x, win->pos_y, &(win->bassDial)))
502 {
503 win->bassDial.start_value = win->bassDial.value;
504 win->active_dial = PORT_BASS;
505 }
506 else if (is_point_in_dial_area(win->pos_x, win->pos_y, &(win->middleDial)))
507 {
508 win->middleDial.start_value = win->middleDial.value;
509 win->active_dial = PORT_MIDDLE;
510 }
511 else if (is_point_in_dial_area(win->pos_x, win->pos_y, &(win->trebleDial)))
512 {
513 win->trebleDial.start_value = win->trebleDial.value;
514 win->active_dial = PORT_TREBLE;
515 }
516 else if (is_point_in_dial_area(win->pos_x, win->pos_y, &(win->voiceDial)))
517 {
518 win->voiceDial.start_value = win->voiceDial.value;
519 win->active_dial = PORT_VOICE;
520 }
521 else if (is_point_in_dial_area(win->pos_x, win->pos_y, &(win->volumeDial)))
522 {
523 win->volumeDial.start_value = win->volumeDial.value;
524 win->active_dial = PORT_LEVEL;
525 }
526 // Click on bypass button
527 else if (is_point_in_area(win->pos_x, win->pos_y, 275, 459, 326, 511))
528 {
529 // Toggle bypass flag
530 win->bypass_flag ^= 1;
531 float value = win->bypass_flag;
532 win->write_function(win->controller,PORT_BYPASS,sizeof(float),0,&value);
533 win->active_dial = -1;
534 win_draw(win);
535 }
536 else
537 {
538 win->active_dial = -1;
539 }
540 }
541 }
542 break;
543 case XCB_MOTION_NOTIFY: // Mouse move while left button is pressed
544 {
545 xcb_motion_notify_event_t *mev = (xcb_motion_notify_event_t *) event;
546
547 if (win->active_dial != -1)
548 {
549 // Recalculate active Dial value and send new value to the
550 // main LV2 plugin module
551 switch (win->active_dial)
552 {
553 float value;
554 case PORT_DRIVE:
555 win->driveDial.value = clamp(win->driveDial.start_value + win->pos_y - mev->event_y);
556 value = win->driveDial.value;
557 win->write_function(win->controller,PORT_DRIVE,sizeof(float),0,&value);
558 break;
559 case PORT_BASS:
560 win->bassDial.value = clamp(win->bassDial.start_value + win->pos_y - mev->event_y);
561 value = value_to_db(win->bassDial.value, 15.0);
562 win->write_function(win->controller,PORT_BASS,sizeof(float),0,&value);
563 break;
564 case PORT_MIDDLE:
565 win->middleDial.value = clamp(win->middleDial.start_value + win->pos_y - mev->event_y);
566 value = value_to_db(win->middleDial.value, 15.0);
567 win->write_function(win->controller,PORT_MIDDLE,sizeof(float),0,&value);
568 break;
569 case PORT_TREBLE:
570 win->trebleDial.value = clamp(win->trebleDial.start_value + win->pos_y - mev->event_y);
571 value = value_to_db(win->trebleDial.value, 15.0);
572 win->write_function(win->controller,PORT_TREBLE,sizeof(float),0,&value);
573 break;
574 case PORT_VOICE:
575 win->voiceDial.value = clamp(win->voiceDial.start_value + win->pos_y - mev->event_y);
576 value = win->voiceDial.value / 100.0;
577 win->write_function(win->controller,PORT_VOICE,sizeof(float),0,&value);
578 break;
579 case PORT_LEVEL:
580 win->volumeDial.value = clamp(win->volumeDial.start_value + win->pos_y - mev->event_y);
581 value = win->volumeDial.value / 100.0;
582 win->write_function(win->controller,PORT_LEVEL,sizeof(float),0,&value);
583 }
584 win_draw(win);
585 }
586 }
587 break;
588 }
589 free(event);
590 }
591 xcb_flush(win->connection);
592 }
593
594 // LV2 callback function to organize event handling
595 static int
idle(LV2UI_Handle handle)596 idle(LV2UI_Handle handle)
597 {
598 win_t *win = (win_t *)handle;
599 win_handle_events(win);
600
601 return 0;
602 }
603
604
605 // LV2 interfaces and descriptors
606 static const LV2UI_Idle_Interface idle_iface = { idle };
607
608 static const void*
extension_data(const char * uri)609 extension_data(const char* uri)
610 {
611 if (!strcmp(uri, LV2_UI__idleInterface))
612 {
613 return &idle_iface;
614 }
615 return NULL;
616 }
617
618 static const LV2UI_Descriptor descriptor =
619 {
620 PLUGIN_URI "ui",
621 (void* (*)(const LV2UI_Descriptor*, const char*, const char*, void (*)(void*, unsigned int, unsigned int, unsigned int, const void*), void*, void**, const LV2_Feature* const*))instantiate,
622 cleanup,
623 port_event,
624 extension_data
625 };
626
627 LV2_SYMBOL_EXPORT
628 const LV2UI_Descriptor*
lv2ui_descriptor(uint32_t index)629 lv2ui_descriptor(uint32_t index)
630 {
631 switch (index)
632 {
633 case 0:
634 return &descriptor;
635 default:
636 return NULL;
637 }
638 }
639
640
641
642
643
644
645
646
647