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