1 /*
2  * Copyright (C) 2019-2020 Alexandros Theodotou <alex at zrythm dot org>
3  *
4  * This file is part of ZPlugins
5  *
6  * ZPlugins is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as
8  * published by the Free Software Foundation, either version 3 of the
9  * License, or (at your option) any later version.
10  *
11  * ZPlugins is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Affero Public License
17  * along with ZPlugins.  If not, see <https://www.gnu.org/licenses/>.
18  *
19  * This file incorporates work covered by the following copyright and
20  * permission notice:
21  *
22   Copyright 2012-2019 David Robillard <http://drobilla.net>
23 
24   Permission to use, copy, modify, and/or distribute this software for any
25   purpose with or without fee is hereby granted, provided that the above
26   copyright notice and this permission notice appear in all copies.
27 
28   THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
29   WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
30   MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
31   ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
32   WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
33   ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
34   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
35 */
36 
37 #include PLUGIN_CONFIG
38 
39 #include <math.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 
43 #include PLUGIN_COMMON
44 #include "lfo_math.h"
45 #include "ui_theme.h"
46 
47 #include <cairo.h>
48 
49 #include <lv2/atom/util.h>
50 #include <lv2/ui/ui.h>
51 
52 #include <ztoolkit/ztk.h>
53 
54 /** Width and height of the window. */
55 #define WIDTH 480
56 #define HEIGHT 261
57 
58 #define LEFT_BTN_WIDTH 40
59 #define TOP_BTN_HEIGHT 38
60 #define MID_REGION_WIDTH 394
61 #define MID_BTN_WIDTH 193
62 #define MID_REGION_HEIGHT 180
63 #define SYNC_RATE_BOX_WIDTH 46
64 #define SYNC_RATE_BOX_HEIGHT 16
65 #define FREQ_BOX_WIDTH 48
66 #define ARROW_BTN_WIDTH 15
67 #define RANGE_POINT_WIDTH 10
68 #define RANGE_HEIGHT 150
69 #define NODE_WIDTH 12
70 #define GRID_HPADDING 26
71 #define GRID_SPACE 42
72 #define GRID_WIDTH (8 * GRID_SPACE)
73 #define GRID_XSTART_GLOBAL \
74   (LEFT_BTN_WIDTH + 4 + GRID_HPADDING)
75 #define GRID_XEND_GLOBAL \
76   (LEFT_BTN_WIDTH + 4 + GRID_HPADDING + \
77    8 * GRID_SPACE)
78 #define GRID_YSTART_OFFSET 46
79 #define GRID_YEND_OFFSET 164
80 #define GRID_YSTART_GLOBAL \
81     (TOP_BTN_HEIGHT + 2 + GRID_YSTART_OFFSET)
82 #define GRID_YEND_GLOBAL \
83     (TOP_BTN_HEIGHT + 2 + GRID_YEND_OFFSET)
84 #define GRID_HEIGHT \
85   (GRID_YEND_OFFSET - GRID_YSTART_OFFSET)
86 #define RANGE_STARTX 461
87 
88 #define GRAPH_OVERLAY_ALPHA 0.6
89 
90 /** Double click interval, in seconds. */
91 #define DOUBLE_CLICK_INTERVAL 0.24
ExecHash(PlanState * pstate)92 
93 #define GET_HANDLE \
94   LfoUi * self = (LfoUi *) puglGetHandle (view);
95 
96 typedef struct ZtkApp ZtkApp;
97 
98 typedef enum LeftButton
99 {
100   LEFT_BTN_SINE,
101   LEFT_BTN_TRIANGLE,
102   LEFT_BTN_SAW,
103   LEFT_BTN_SQUARE,
104   LEFT_BTN_CUSTOM,
105   NUM_LEFT_BUTTONS,
MultiExecHash(HashState * node)106 } LeftButton;
107 
108 typedef enum TopButton
109 {
110   TOP_BTN_CURVE,
111   TOP_BTN_STEP,
112   NUM_TOP_BUTTONS,
113 } TopButton;
114 
115 typedef enum BotButton
116 {
117   BOT_BTN_SYNC,
118   BOT_BTN_FREE,
119   NUM_BOT_BUTTONS,
120 } BotButton;
121 
122 typedef enum GridButton
123 {
124   GRID_BTN_SNAP,
125   GRID_BTN_HMIRROR,
126   GRID_BTN_VMIRROR,
127   NUM_GRID_BUTTONS,
128 } GridButton;
129 
130 typedef enum LabelType
131 {
132   LBL_TYPE_INVERT,
133   LBL_TYPE_SHIFT,
134   NUM_LBL_TYPES,
135 } LabelType;
136 
137 typedef enum DrawDataType
138 {
MultiExecPrivateHash(HashState * node)139   DATA_TYPE_BTN_TOP,
140   DATA_TYPE_BTN_LEFT,
141   DATA_TYPE_BTN_BOT,
142   DATA_TYPE_BTN_GRID,
143   DATA_TYPE_LBL,
144 } DrawDataType;
145 
146 typedef struct LfoUi
147 {
148   /** Port values. */
149   float            gate;
150   int              trigger;
151   float            cv_gate;
152   float            cv_trigger;
153   float            freq;
154   float            shift;
155   float            range_min;
156   float            range_max;
157   int              step_mode;
158   int              freerun;
159   int              hinvert;
160   int              vinvert;
161   int              sine_on;
162   int              saw_on;
163   int              square_on;
164   int              triangle_on;
165   int              custom_on;
166   float            sync_rate;
167   float            sync_rate_type;
168   float            grid_step;
169   float            nodes[16][3];
170   int              num_nodes;
171 
172   /* Non-port values */
173 
174   LfoCommon       common;
175 
176   LV2UI_Write_Function write;
177   LV2UI_Controller controller;
178 
179   /**
180    * This is the window passed in the features from
181    * the host.
182    *
183    * The pugl window will be wrapped in here.
184    */
185   void *           parent_window;
186 
187   /**
188    * Resize handle for the parent window.
189    */
190   LV2UI_Resize*    resize;
191 
192   /** Pointer to the mid region widget, to use
193    * for redisplaying only its rect. */
194   ZtkWidget *      mid_region;
195 
196   /** Widgets for the current nodes. */
197   ZtkWidget *      node_widgets[16];
198 
199   /** Cache to remember when last double click
200    * occured, so that it can be ignored. */
201   double           last_double_click;
202 
203   /** Timestamp of last delete click, so we
204    * don't delete more than once. */
205   double           last_delete_click;
206 
207   /** Index of the current node being dragged,
208    * or -1. */
209   int              dragging_node;
210 
211   /** This is double here so we can be more
212    * precise with calculations. */
213   double           current_sample;
214 
MultiExecParallelHash(HashState * node)215   /** Last time the current sample was set at. */
216   gint64           last_current_sample_set;
217 
218   /** Whether we need to recalculate the caches. */
219   int              has_change;
220 
221   ZtkRect          last_rect;
222 
223   /** Caches. */
224   float            sine_cache[GRID_WIDTH];
225   float            saw_cache[GRID_WIDTH];
226 
227   char             bundle_path[2000];
228 
229   LfoUiTheme      ui_theme;
230 
231   cairo_t *        cached_cr;
232   cairo_surface_t * cached_surface;
233 
234   ZtkApp *         app;
235 } LfoUi;
236 
237 /**
238  * Data to be passed around in the callbacks.
239  */
240 typedef struct DrawData
241 {
242   /** Enum value corresponding to the type. */
243   int            val;
244   DrawDataType   type;
245   LfoUi *       zlfo_ui;
246 } DrawData;
247 
248 #define SEND_PORT_EVENT(_self,idx,val) \
249   { \
250     float fval = (float) val; \
251     _self->write ( \
252        _self->controller, (uint32_t) idx, \
253        sizeof (float), 0, &fval); \
254   }
255 
256 #define GENERIC_GETTER(sc) \
257 static float \
258 sc##_getter ( \
259   ZtkControl * control, \
260   LfoUi *     self) \
261 { \
262   return self->sc; \
263 }
264 
265 #define DEFINE_GET_SET(caps,sc) \
266 GENERIC_GETTER (sc); \
267 static void \
268 sc##_setter ( \
269   ZtkControl * control, \
270   LfoUi *     self, \
271   float        val) \
272 { \
273   self->sc = val; \
274   ztk_debug ( \
275     "setting " #sc " to %f", (double) val); \
276   SEND_PORT_EVENT (self, LFO_##caps, self->sc); \
277   self->has_change = 1; \
278 }
279 
280 DEFINE_GET_SET (SHIFT, shift);
281 DEFINE_GET_SET (SYNC_RATE, sync_rate);
282 DEFINE_GET_SET (FREQ, freq);
283 DEFINE_GET_SET (RANGE_MIN, range_min);
284 DEFINE_GET_SET (RANGE_MAX, range_max);
285 
286 #undef GENERIC_GETTER
287 #undef DEFINE_GET_SET
288 
289 static void
290 node_pos_setter (
291   LfoUi *     self,
292   unsigned int idx,
293   float        val)
294 {
295   self->nodes[idx][0] = val;
296   SEND_PORT_EVENT (
297     self, LFO_NODE_1_POS + idx * 3, val);
298   self->has_change = 1;
299 }
300 
301 static void
302 node_val_setter (
303   LfoUi *     self,
304   unsigned int idx,
305   float        val)
306 {
307   self->nodes[idx][1] = val;
308   SEND_PORT_EVENT (
309     self, LFO_NODE_1_VAL + idx * 3, val);
310   self->has_change = 1;
311 }
312 
313 /**
314  * Resets a surface and cairo_t with a new surface
315  * and cairo_t based on the given rectangle and
316  * cairo_t.
317  *
318  * To be used inside draw calls of widgets that
319  * use caching.
320  */
321 static void
322 z_cairo_reset_caches (
323   cairo_t **         cr_cache,
324   cairo_surface_t ** surface_cache,
325   int                width,
326   int                height,
327   cairo_t *          new_cr)
328 {
329   if (*surface_cache)
330     cairo_surface_destroy (
331       *surface_cache);
332   if (*cr_cache)
333     cairo_destroy (*cr_cache);
334 
335   *surface_cache =
336     cairo_surface_create_similar (
337       cairo_get_target (new_cr),
338       CAIRO_CONTENT_COLOR_ALPHA,
339       width, height);
340   *cr_cache =
341     cairo_create (*surface_cache);
342 }
343 
344 static void
345 num_nodes_setter (
346   LfoUi *     self,
347   int          val)
348 {
349   self->num_nodes = val;
350   SEND_PORT_EVENT (
351     self, LFO_NUM_NODES, val);
352   self->has_change = 1;
353 }
ExecInitHash(Hash * node,EState * estate,int eflags)354 
355 static void
356 bg_draw_cb (
357   ZtkWidget * widget,
358   cairo_t *   cr,
359   ZtkRect *   draw_rect,
360   LfoUi *    self)
361 {
362   /* clear background to black first */
363   cairo_set_source_rgba (cr, 0, 0, 0, 1);
364   cairo_rectangle (
365     cr, widget->rect.x, widget->rect.y,
366     widget->rect.width, widget->rect.height);
367   cairo_fill (cr);
368 
369   /* set theme background */
370   zlfo_ui_theme_set_cr_color (
371     &self->ui_theme, cr, bg);
372   cairo_rectangle (
373     cr, widget->rect.x, widget->rect.y,
374     widget->rect.width, widget->rect.height);
375   cairo_fill (cr);
376 }
377 
378 static void
379 add_bg_widget (
380   LfoUi * self)
381 {
382   ZtkRect rect = {
383     0, 0, self->app->width, self->app->height };
384   ZtkDrawingArea * da =
385     ztk_drawing_area_new (
386       &rect, NULL,
387       (ZtkWidgetDrawCallback) bg_draw_cb,
388       NULL, self);
389   ztk_app_add_widget (
390     self->app, (ZtkWidget *) da, 0);
391 }
392 
393 typedef struct ComboBoxElement
394 {
395   int           id;
396   char          label[600];
397   ZtkComboBox * combo;
398   LfoUi *      zlfo_ui;
399 } ComboBoxElement;
400 
401 static void
402 sync_rate_type_activate_cb (
403   ZtkWidget *       widget,
404   ComboBoxElement * el)
405 {
406   ztk_debug (
ExecEndHash(HashState * node)407     "activate %p %d %s", el->combo,
408     el->id, el->label);
409   LfoUi * self = el->zlfo_ui;
410   self->sync_rate_type = (float) el->id;
411   SEND_PORT_EVENT (
412     self, LFO_SYNC_RATE_TYPE,
413     self->sync_rate_type);
414   self->has_change = 1;
415 }
416 
417 static void
418 grid_step_activate_cb (
419   ZtkWidget *       widget,
420   ComboBoxElement * el)
421 {
422   /*ztk_debug (*/
423     /*"activate %p %d %s", el->combo,*/
424     /*el->id, el->label);*/
425   LfoUi * self = el->zlfo_ui;
426   self->grid_step = (float) el->id;
427   SEND_PORT_EVENT (
428     self, LFO_GRID_STEP, self->grid_step);
429   self->has_change = 1;
430 }
ExecHashTableCreate(HashState * state,List * hashOperators,List * hashCollations,bool keepNulls)431 
432 /**
433  * Called when one of the buttons was clicked.
434  */
435 static void
436 on_btn_clicked (
437   ZtkWidget * widget,
438   DrawData *  data)
439 {
440   /*ztk_debug ("Button clicked!");*/
441 
442   LfoUi * self = data->zlfo_ui;
443   switch (data->type)
444     {
445     case DATA_TYPE_BTN_TOP:
446       switch (data->val)
447         {
448         case TOP_BTN_CURVE:
449           self->step_mode = 0;
450           SEND_PORT_EVENT (
451             self, LFO_STEP_MODE, self->step_mode);
452           self->has_change = 1;
453           break;
454         case TOP_BTN_STEP:
455           self->step_mode = 1;
456           SEND_PORT_EVENT (
457             self, LFO_STEP_MODE, self->step_mode);
458           self->has_change = 1;
459           break;
460         }
461       break;
462     case DATA_TYPE_BTN_LEFT:
463       switch (data->val)
464         {
465 #define HANDLE_BTN(caps,lowercase) \
466   case LEFT_BTN_##caps: \
467     if (self->lowercase##_on) \
468       { \
469         self->lowercase##_on = 0; \
470         SEND_PORT_EVENT ( \
471           self, LFO_##caps##_TOGGLE, 0.f); \
472         self->has_change = 1; \
473       } \
474     else \
475       { \
476         self->lowercase##_on = 1; \
477         SEND_PORT_EVENT ( \
478           self, LFO_##caps##_TOGGLE, 1.f); \
479         self->has_change = 1; \
480       } \
481     break
482           HANDLE_BTN (SINE, sine);
483           HANDLE_BTN (SAW, saw);
484           HANDLE_BTN (TRIANGLE, triangle);
485           HANDLE_BTN (SQUARE, square);
486           HANDLE_BTN (CUSTOM, custom);
487           self->has_change = 1;
488         default:
489           break;
490         }
491       break;
492     case DATA_TYPE_BTN_BOT:
493       switch (data->val)
494         {
495         case BOT_BTN_SYNC:
496           self->freerun = 0;
497           SEND_PORT_EVENT (
498             self, LFO_FREE_RUNNING, self->freerun);
499           self->has_change = 1;
500           break;
501         case BOT_BTN_FREE:
502           self->freerun = 1;
503           SEND_PORT_EVENT (
504             self, LFO_FREE_RUNNING, self->freerun);
505           self->has_change = 1;
506           break;
507         }
508       break;
509     case DATA_TYPE_BTN_GRID:
510       switch (data->val)
511         {
512         case GRID_BTN_SNAP:
513           {
514             ZtkComboBox * combo =
515               ztk_combo_box_new (
516                 widget, 0, 0);
517             ztk_app_add_widget (
518               widget->app, (ZtkWidget *) combo,
519               100);
520             for (int i = 0; i < NUM_GRID_STEPS; i++)
521               {
522                 ComboBoxElement * el =
523                   calloc (
524                     1, sizeof (ComboBoxElement));
525                 el->id = i;
526                 el->combo = combo;
527                 el->zlfo_ui = self;
528                 switch (i)
529                   {
530                   case GRID_STEP_FULL:
531                     strcpy (el->label, "full");
532                     break;
533                   case GRID_STEP_HALF:
534                     strcpy (el->label, "1/2");
535                     break;
536                   case GRID_STEP_FOURTH:
537                     strcpy (el->label, "1/4");
538                     break;
539                   case GRID_STEP_EIGHTH:
540                     strcpy (el->label, "1/8");
541                     break;
542                   case GRID_STEP_SIXTEENTH:
543                     strcpy (el->label, "1/16");
544                     break;
545                   case GRID_STEP_THIRTY_SECOND:
546                     strcpy (el->label, "1/32");
547                     break;
548                   default:
549                     break;
550                   }
551                 ztk_combo_box_add_text_element (
552                   combo, el->label,
553                   (ZtkWidgetActivateCallback)
554                   grid_step_activate_cb, el);
555               }
556           }
557           break;
558         case GRID_BTN_HMIRROR:
559           self->hinvert = !self->hinvert;
560           SEND_PORT_EVENT (
561             self, LFO_HINVERT, self->hinvert);
562           self->has_change = 1;
563           break;
564         case GRID_BTN_VMIRROR:
565           self->vinvert = !self->vinvert;
566           SEND_PORT_EVENT (
567             self, LFO_VINVERT, self->vinvert);
568           self->has_change = 1;
569           break;
570         }
571       break;
572     default:
573       break;
574     }
575 }
576 
577 static void
578 on_sync_rate_type_clicked (
579   ZtkWidget * widget,
580   LfoUi *    self)
581 {
582   ZtkComboBox * combo =
583     ztk_combo_box_new (
584       widget, 1, 0);
585   ztk_app_add_widget (
586     widget->app, (ZtkWidget *) combo, 100);
587 
588   for (int i = 0; i < NUM_SYNC_RATE_TYPES; i++)
589     {
590       ComboBoxElement * el =
591         calloc (1, sizeof (ComboBoxElement));
592       el->id = i;
593       el->combo = combo;
594       el->zlfo_ui = self;
595       switch (i)
596         {
597         case SYNC_TYPE_NORMAL:
598           strcpy (el->label, "normal");
599           break;
600         case SYNC_TYPE_DOTTED:
601           strcpy (el->label, "dotted");
602           break;
603         case SYNC_TYPE_TRIPLET:
604           strcpy (el->label, "triplet");
605           break;
606         default:
607           break;
608         }
609       ztk_combo_box_add_text_element (
610         combo, el->label,
611         (ZtkWidgetActivateCallback)
612         sync_rate_type_activate_cb, el);
613     }
614 }
615 
616 static int
617 get_button_active (
618   ZtkButton * btn,
619   DrawData *  data)
620 {
621   LfoUi * self = data->zlfo_ui;
622 
623   switch (data->type)
624     {
625     case DATA_TYPE_BTN_TOP:
626       switch (data->val)
627         {
628         case TOP_BTN_CURVE:
629           return !self->step_mode;
630           break;
631         case TOP_BTN_STEP:
632           return self->step_mode;
633           break;
634         }
635       break;
636     case DATA_TYPE_BTN_LEFT:
637       switch (data->val)
638         {
639         case LEFT_BTN_SINE:
640           return self->sine_on;
641           break;
642         case LEFT_BTN_TRIANGLE:
643           return self->triangle_on;
644           break;
645         case LEFT_BTN_SAW:
646           return self->saw_on;
647           break;
648         case LEFT_BTN_SQUARE:
649           return self->square_on;
650           break;
651         case LEFT_BTN_CUSTOM:
652           return self->custom_on;
653           break;
654         default:
655           break;
656         }
657       break;
658     case DATA_TYPE_BTN_BOT:
659       switch (data->val)
660         {
661         case BOT_BTN_SYNC:
662           return !self->freerun;
663           break;
664         case BOT_BTN_FREE:
665           return self->freerun;
666           break;
667         }
ExecChooseHashTableSize(double ntuples,int tupwidth,bool useskew,bool try_combined_hash_mem,int parallel_workers,size_t * space_allowed,int * numbuckets,int * numbatches,int * num_skew_mcvs)668       break;
669     case DATA_TYPE_BTN_GRID:
670       switch (data->val)
671         {
672         case GRID_BTN_HMIRROR:
673           return self->hinvert;
674           break;
675         case GRID_BTN_VMIRROR:
676           return self->vinvert;
677           break;
678         }
679       break;
680     case DATA_TYPE_LBL:
681       break;
682     }
683 
684   return 0;
685 }
686 
687 static void
688 add_left_buttons (
689   LfoUi * self)
690 {
691   const int padding = 2;
692   const int width = LEFT_BTN_WIDTH;
693   const int height = 50;
694   for (int i = 0; i < NUM_LEFT_BUTTONS; i++)
695     {
696       ZtkRect rect = {
697         padding, padding + i * (height + padding),
698         width, height };
699       DrawData * data =
700         calloc (1, sizeof (DrawData));
701       data->val = i;
702       data->type = DATA_TYPE_BTN_LEFT;
703       data->zlfo_ui = self;
704       ZtkButton * btn =
705         ztk_button_new (
706           &rect,
707           (ZtkWidgetActivateCallback)
708           on_btn_clicked, data);
709       ztk_button_make_toggled (
710         btn,
711         (ZtkButtonToggledGetter)
712         get_button_active);
713       ztk_button_set_background_colors (
714         btn,
715         &self->ui_theme.button_normal,
716         &self->ui_theme.button_hover,
717         &self->ui_theme.left_button_click);
718 
719 #define MAKE_BUTTON_SVGED(caps,lowercase) \
720   case LEFT_BTN_##caps: \
721     { \
722       ztk_button_make_svged (\
723         btn, hpadding, vpadding, \
724         self->ui_theme.lowercase##_svg, \
725         self->ui_theme.lowercase##_svg, \
726         self->ui_theme.lowercase##_svg); \
727     } \
728     break
729 
730       int hpadding = 8;
731       int vpadding = 4;
732       switch (data->val)
733         {
734           MAKE_BUTTON_SVGED (SINE, sine);
735           MAKE_BUTTON_SVGED (TRIANGLE, triangle);
736           MAKE_BUTTON_SVGED (SAW, saw);
737           MAKE_BUTTON_SVGED (SQUARE, square);
738           MAKE_BUTTON_SVGED (CUSTOM, custom);
739         }
740 
741 #undef MAKE_BUTTON_SVGED
742 
743       ztk_app_add_widget (
744         self->app, (ZtkWidget *) btn, 1);
745     }
746 }
747 
748 static void
749 top_and_bot_btn_bg_cb (
750   ZtkWidget * w,
751   cairo_t *   cr,
752   ZtkRect *   draw_rect,
753   DrawData *  data)
754 {
755   LfoUi * self = data->zlfo_ui;
756 
757   /* set background */
758   ZtkWidgetState state = w->state;
759   int is_normal = 0;
760   int is_pressed = 0;
761   int is_hovered = 0;
762   if (state & ZTK_WIDGET_STATE_PRESSED ||
763       get_button_active ((ZtkButton *) w, data))
764     {
765       zlfo_ui_theme_set_cr_color (
766         &self->ui_theme, cr, selected_bg);
767       is_pressed = 1;
768     }
769   else if (state & ZTK_WIDGET_STATE_HOVERED)
770     {
771       zlfo_ui_theme_set_cr_color (
772         &self->ui_theme, cr, button_hover);
773       is_hovered = 1;
774     }
775   else
776     {
777       zlfo_ui_theme_set_cr_color (
778         &self->ui_theme, cr, button_normal);
779       is_normal = 1;
780     }
781 
782   if (data->type == DATA_TYPE_BTN_TOP)
783     {
784       double height_with_border =
785         is_normal ?
786           /* show border if normal */
787           w->rect.height - 2 :
788           w->rect.height + 1;
789 
790       cairo_rectangle (
791         cr, w->rect.x, w->rect.y, w->rect.width,
792         height_with_border);
793       cairo_fill (cr);
794 
795       /* draw line on bot */
796       if (is_pressed)
797         {
798           zlfo_ui_theme_set_cr_color (
799             &self->ui_theme, cr, button_lining_active);
800         }
801       else if (is_hovered)
802         {
803           zlfo_ui_theme_set_cr_color (
804             &self->ui_theme, cr, button_lining_hover);
805         }
806       cairo_rectangle (
807         cr, w->rect.x,
808         w->rect.y + height_with_border - 4, w->rect.width,
809         4);
810       cairo_fill (cr);
811     }
812   else if (data->type == DATA_TYPE_BTN_BOT)
813     {
814       double y_with_border =
815         is_normal ?
816           /* show border if normal */
817           w->rect.y :
818           w->rect.y - 3;
819       double height_with_border =
820         is_normal ?
821           /* show border if normal */
822           w->rect.height :
823           w->rect.height + 3;
824 
825       cairo_rectangle (
826         cr, w->rect.x,
827         y_with_border,
828         w->rect.width,
829         height_with_border);
830       cairo_fill (cr);
831 
832       /* draw line on top */
833       if (is_pressed)
834         {
835           zlfo_ui_theme_set_cr_color (
836             &self->ui_theme, cr, button_lining_active);
837         }
838       else if (is_hovered)
839         {
840           zlfo_ui_theme_set_cr_color (
841             &self->ui_theme, cr, button_lining_hover);
842         }
843       cairo_rectangle (
844         cr, w->rect.x,
845         y_with_border, w->rect.width,
846         4);
847       cairo_fill (cr);
848     }
849 
850   if (data->type == DATA_TYPE_BTN_BOT)
851     {
852 
853 #define DRAW_SVG(caps,svg) \
854   case BOT_BTN_##caps: \
855   { \
856     int hpadding = 0; \
857     int vpadding = 0; \
858     ZtkRect rect = { \
859       (w->rect.x + hpadding), \
860       w->rect.y + vpadding, \
861       w->rect.width - hpadding * 2, \
862       w->rect.height - vpadding * 2 }; \
863     if (data->val == BOT_BTN_SYNC) \
864       { \
865         rect.x -=  \
866           (SYNC_RATE_BOX_WIDTH + \
867              ARROW_BTN_WIDTH) / 2.0; \
868       } \
869     else if (data->val == BOT_BTN_FREE) \
870       { \
871         rect.x -= FREQ_BOX_WIDTH / 2.0; \
872       } \
ExecHashTableDestroy(HashJoinTable hashtable)873     ztk_rsvg_draw ( \
874       self->ui_theme.svg##_svg, cr, &rect); \
875   } \
876       break
877 
878       switch (data->val)
879         {
880           DRAW_SVG (SYNC, sync);
881           DRAW_SVG (FREE, freeb);
882         }
883 
884 #undef DRAW_SVG
885     }
886 }
887 
888 static void
889 add_top_buttons (
890   LfoUi * self)
891 {
892   const int padding = 2;
893   const int width = MID_BTN_WIDTH;
894   const int height = TOP_BTN_HEIGHT;
895   const int start = LEFT_BTN_WIDTH + padding;
896   for (int i = 0; i < NUM_TOP_BUTTONS; i++)
897     {
898       ZtkRect rect = {
899         start + padding + i * (width + padding),
900         padding, width, height };
901       DrawData * data =
902         calloc (1, sizeof (DrawData));
903       data->val = i;
904       data->type = DATA_TYPE_BTN_TOP;
905       data->zlfo_ui = self;
ExecHashIncreaseNumBatches(HashJoinTable hashtable)906       ZtkButton * btn =
907         ztk_button_new (
908           &rect,
909           (ZtkWidgetActivateCallback)
910           on_btn_clicked, data);
911       ztk_button_add_background_callback (
912         btn,
913         (ZtkWidgetDrawCallback)
914         top_and_bot_btn_bg_cb);
915       ztk_button_make_toggled (
916         btn,
917         (ZtkButtonToggledGetter)
918         get_button_active);
919 
920 #define MAKE_BUTTON_SVGED(caps,lowercase) \
921   case TOP_BTN_##caps: \
922     { \
923       ztk_button_make_svged (\
924         btn, hpadding, vpadding, \
925         self->ui_theme.lowercase##_svg, \
926         self->ui_theme.lowercase##_svg, \
927         self->ui_theme.lowercase##_svg); \
928     } \
929     break
930 
931       int hpadding = 6;
932       int vpadding = 6;
933       switch (data->val)
934         {
935           MAKE_BUTTON_SVGED (CURVE, curve);
936           MAKE_BUTTON_SVGED (STEP, step);
937         }
938 
939 #undef MAKE_BUTTON_SVGED
940 
941       ztk_app_add_widget (
942         self->app, (ZtkWidget *) btn, 1);
943     }
944 }
945 
946 static void
947 sync_rate_control_draw_cb (
948   ZtkWidget * widget,
949   cairo_t *   cr,
950   ZtkRect *   draw_rect,
951   LfoUi *    self)
952 {
953   /* draw black bg */
954   cairo_set_source_rgba (
955     cr, 0, 0, 0, 1);
956   cairo_rectangle (
957     cr, widget->rect.x, widget->rect.y,
958     widget->rect.width, widget->rect.height);
959   cairo_fill (cr);
960 
961   /* draw label */
962   int val = (int) self->sync_rate;
963   char lbl[12] = "\0";
964   switch (val)
965     {
966     case SYNC_1_128:
967       strcpy (lbl, "1 / 128");
968       break;
969     case SYNC_1_64:
970       strcpy (lbl, "1 / 64");
971       break;
972     case SYNC_1_32:
973       strcpy (lbl, "1 / 32");
974       break;
975     case SYNC_1_16:
976       strcpy (lbl, "1 / 16");
977       break;
978     case SYNC_1_8:
979       strcpy (lbl, "1 / 8");
980       break;
981     case SYNC_1_4:
982       strcpy (lbl, "1 / 4");
983       break;
984     case SYNC_1_2:
985       strcpy (lbl, "1 / 2");
986       break;
987     case SYNC_1_1:
988       strcpy (lbl, "1 / 1");
989       break;
990     case SYNC_2_1:
991       strcpy (lbl, "2 / 1");
992       break;
993     case SYNC_4_1:
994       strcpy (lbl, "4 / 1");
995       break;
996     case SYNC_8_1:
997       strcpy (lbl, "8 / 1");
998       break;
999     case SYNC_16_1:
1000       strcpy (lbl, "16 / 1");
1001       break;
1002     case SYNC_32_1:
1003       strcpy (lbl, "32 / 1");
1004       break;
1005     case SYNC_64_1:
1006       strcpy (lbl, "64 / 1");
1007       break;
1008     case SYNC_128_1:
1009       strcpy (lbl, "128 / 1");
1010       break;
1011       break;
1012     }
1013   switch ((int) self->sync_rate_type)
1014     {
1015     case SYNC_TYPE_DOTTED:
1016       strcat (lbl, ".");
1017       break;
1018     case SYNC_TYPE_TRIPLET:
1019       strcat (lbl, "t");
1020       break;
1021     default:
1022       break;
1023     }
1024 
1025   /* Draw label */
1026   cairo_text_extents_t extents;
1027   cairo_set_font_size (cr, 10);
1028   cairo_text_extents (cr, lbl, &extents);
1029   cairo_move_to (
1030     cr,
1031     (widget->rect.x + widget->rect.width / 2.0) -
1032       (extents.width / 2.0 + 1.0),
1033     (widget->rect.y + widget->rect.height) -
1034       widget->rect.height / 2.0 +
1035         extents.height / 2.0);
1036   cairo_set_source_rgba (cr, 1, 1, 1, 1);
1037   cairo_show_text (cr, lbl);
1038 }
1039 
1040 static void
1041 freq_control_draw_cb (
1042   ZtkWidget * widget,
1043   cairo_t *   cr,
1044   ZtkRect *   draw_rect,
1045   LfoUi *    self)
1046 {
1047   /* draw black bg */
1048   cairo_set_source_rgba (
1049     cr, 0, 0, 0, 1);
1050   cairo_rectangle (
1051     cr, widget->rect.x, widget->rect.y,
1052     widget->rect.width, widget->rect.height);
1053   cairo_fill (cr);
1054 
1055   /* draw label */
1056   char lbl[12];
1057   if (self->freq < 1.f)
1058     sprintf (lbl, "%.2f Hz", (double) self->freq);
1059   else
1060     sprintf (lbl, "%.1f Hz", (double) self->freq);
1061 
1062   /* Draw label */
1063   cairo_text_extents_t extents;
1064   cairo_set_font_size (cr, 10);
1065   cairo_text_extents (cr, lbl, &extents);
1066   cairo_move_to (
1067     cr,
1068     (widget->rect.x + widget->rect.width / 2.0) -
1069       (extents.width / 2.0 + 1.0),
1070     (widget->rect.y + widget->rect.height) -
1071       widget->rect.height / 2.0 +
1072         extents.height / 2.0);
1073   cairo_set_source_rgba (cr, 1, 1, 1, 1);
1074   cairo_show_text (cr, lbl);
1075 }
1076 
ExecParallelHashIncreaseNumBatches(HashJoinTable hashtable)1077 static int
1078 sync_rate_control_btn_event_cb (
1079   ZtkWidget *             w,
1080   const PuglEventButton * btn,
1081   LfoUi *                self)
1082 {
1083   ZtkWidgetState state = w->state;
1084   if (state & ZTK_WIDGET_STATE_PRESSED)
1085     {
1086       if (ztk_widget_is_hit (w, btn->x, btn->y))
1087         {
1088           self->freerun = 0;
1089           SEND_PORT_EVENT (
1090             self, LFO_FREE_RUNNING, self->freerun);
1091           self->has_change = 1;
1092         }
1093     }
1094   return 1;
1095 }
1096 
1097 static int
1098 freq_control_btn_event_cb (
1099   ZtkWidget *             w,
1100   const PuglEventButton * btn,
1101   LfoUi *                self)
1102 {
1103   ZtkWidgetState state = w->state;
1104   if (state & ZTK_WIDGET_STATE_PRESSED)
1105     {
1106       if (ztk_widget_is_hit (w, btn->x, btn->y))
1107         {
1108           self->freerun = 1;
1109           SEND_PORT_EVENT (
1110             self, LFO_FREE_RUNNING, self->freerun);
1111           self->has_change = 1;
1112         }
1113     }
1114   return 1;
1115 }
1116 
1117 static void
1118 add_bot_buttons (
1119   LfoUi * self)
1120 {
1121   const int padding = 2;
1122   const int width = MID_BTN_WIDTH;
1123   const int height = TOP_BTN_HEIGHT;
1124   const int start = LEFT_BTN_WIDTH + padding;
1125   for (int i = 0; i < NUM_BOT_BUTTONS; i++)
1126     {
1127       ZtkRect rect = {
1128         start + padding + i * (width + padding),
1129         TOP_BTN_HEIGHT + 4 + MID_REGION_HEIGHT,
1130         width, height };
1131       DrawData * data =
1132         calloc (1, sizeof (DrawData));
1133       data->val = i;
1134       data->type = DATA_TYPE_BTN_BOT;
1135       data->zlfo_ui = self;
1136       ZtkButton * btn =
1137         ztk_button_new (
1138           &rect,
1139           (ZtkWidgetActivateCallback)
1140           on_btn_clicked, data);
1141       ztk_button_add_background_callback (
1142         btn,
1143         (ZtkWidgetDrawCallback)
1144         top_and_bot_btn_bg_cb);
1145       ztk_button_make_toggled (
1146         btn,
1147         (ZtkButtonToggledGetter)
1148         get_button_active);
1149 
1150       ztk_app_add_widget (
1151         self->app, (ZtkWidget *) btn, 1);
1152     }
1153 
1154   /* add sync rate control */
1155   ZtkRect rect = {
1156     (start + padding + width / 2) -
1157       ARROW_BTN_WIDTH / 2,
1158     MID_REGION_HEIGHT + TOP_BTN_HEIGHT + 14,
1159     SYNC_RATE_BOX_WIDTH, SYNC_RATE_BOX_HEIGHT };
1160   ZtkControl * control =
1161     ztk_control_new (
1162       &rect,
1163       (ZtkControlGetter) sync_rate_getter,
1164       (ZtkControlSetter) sync_rate_setter,
1165       (ZtkWidgetDrawCallback)
1166       sync_rate_control_draw_cb,
1167       ZTK_CTRL_DRAG_VERTICAL,
1168       self, 0.f, (float) (NUM_SYNC_RATES - 1),
1169       0.0f);
1170   ((ZtkWidget *) control)->user_data = self;
1171   control->sensitivity = 0.008f;
1172   ((ZtkWidget *) control)->button_event_cb =
1173     (ZtkWidgetButtonEventCallback)
1174     sync_rate_control_btn_event_cb;
1175   ztk_app_add_widget (
1176     self->app, (ZtkWidget *) control, 2);
1177 
1178   /* add sync rate type dropdown */
1179   rect.x =
1180     (start + padding + width / 2 +
1181        SYNC_RATE_BOX_WIDTH) -
1182     (ARROW_BTN_WIDTH / 2 + 1);
1183   rect.y =
1184     MID_REGION_HEIGHT + TOP_BTN_HEIGHT + 14;
1185   rect.width = ARROW_BTN_WIDTH;
1186   rect.height = SYNC_RATE_BOX_HEIGHT;
1187   ZtkButton * btn =
1188     ztk_button_new (
1189       &rect,
1190       (ZtkWidgetActivateCallback)
1191       on_sync_rate_type_clicked, self);
1192   ZtkColor bg = { 0, 0, 0, 1 };
1193   ztk_button_set_background_colors (
1194     btn,
1195     &bg,
1196     &self->ui_theme.button_hover,
1197     &self->ui_theme.bright_click);
1198   ztk_button_make_svged (
1199     btn, 3, 0,
1200     self->ui_theme.down_arrow_svg,
1201     self->ui_theme.down_arrow_svg,
1202     self->ui_theme.down_arrow_svg);
1203   ztk_app_add_widget (
1204     self->app, (ZtkWidget *) btn, 4);
1205 
1206   /* add frequency control */
1207   rect.x =
1208     start + padding + width + padding + width / 2;
1209   rect.y =
1210     MID_REGION_HEIGHT + TOP_BTN_HEIGHT + 14;
1211   rect.width = FREQ_BOX_WIDTH;
1212   rect.height = SYNC_RATE_BOX_HEIGHT;
1213   control =
1214     ztk_control_new (
1215       &rect,
1216       (ZtkControlGetter) freq_getter,
1217       (ZtkControlSetter) freq_setter,
1218       (ZtkWidgetDrawCallback)
1219       freq_control_draw_cb,
1220       ZTK_CTRL_DRAG_VERTICAL,
1221       self, MIN_FREQ, MAX_FREQ, MIN_FREQ);
1222   ((ZtkWidget *) control)->user_data = self;
1223   control->sensitivity = 0.00096f;
1224   ((ZtkWidget *) control)->button_event_cb =
1225     (ZtkWidgetButtonEventCallback)
1226     freq_control_btn_event_cb;
1227   ztk_app_add_widget (
1228     self->app, (ZtkWidget *) control, 2);
1229 }
1230 
1231 /**
1232  * Draws the graphs in curve mode.
1233  */
1234 static void
1235 draw_graph (
1236   LfoUi *  self,
1237   cairo_t * cr)
1238 {
1239   double max_range =
1240     MAX (self->range_max, self->range_min);
1241   double min_range =
1242     MIN (self->range_max, self->range_min);
1243   double range = max_range - min_range;
1244 
1245   double grid_step_divisor =
1246     (double)
1247     grid_step_to_divisor (
1248       (GridStep) self->grid_step);
1249   double step_px = GRID_WIDTH / grid_step_divisor;
1250 
1251   /* sort node curves by position */
1252   NodeIndexElement node_indices[self->num_nodes];
1253   sort_node_indices_by_pos (
1254     self->nodes, node_indices,
1255     self->num_nodes);
1256 
1257   if (self->has_change)
1258     {
1259       for (int i = 0; i < GRID_WIDTH; i++)
1260         {
1261           self->sine_cache[i] =
1262             sinf (
1263               ((float) i * 2.f * PI) /
1264               (float) GRID_WIDTH);
1265           self->saw_cache[i] =
1266             (1.f - (float) i / (float) GRID_WIDTH) *
1267             2.f - 1.f;
1268         }
1269       /*g_message ("has change");*/
1270     }
1271 
1272   cairo_set_source_rgba (
1273     cr, self->ui_theme.left_button_click.red,
1274     self->ui_theme.left_button_click.green,
1275     self->ui_theme.left_button_click.blue,
1276     GRAPH_OVERLAY_ALPHA);
1277   cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
1278   if (self->step_mode)
1279     cairo_set_line_width (cr, step_px);
1280   else
1281     cairo_set_line_width (cr, 6);
1282   double prev_draw_sine,
1283          prev_draw_triangle,
1284          prev_draw_saw, prev_draw_square,
1285          prev_draw_custom;
1286   int i = 0;
1287   double idouble = 0;
1288   if (self->step_mode)
1289     {
1290       idouble = step_px / 2.0;
1291       i = (int) idouble;
1292     }
1293    /* we are approximating so be sure it
ExecParallelHashRepartitionFirst(HashJoinTable hashtable)1294     * doesn't go beyond the width by small
1295     * decimals */
1296   while (idouble < GRID_WIDTH - 0.01)
1297     {
1298       /* from 0 to GRID_WIDTH */
1299       long xvall = (long) i;
1300       double xvald = (double) i;
1301 
1302       xvall =
1303         invert_and_shift_xval (
1304           i, GRID_WIDTH, self->hinvert,
1305           self->shift);
1306       xvald = (double) xvall;
1307 
1308 #define DRAW_VAL(val) \
1309   /* invert vertically */ \
1310   if (self->vinvert) \
1311     { \
1312       val = - val; \
1313     } \
1314  \
1315   /* adjust range */ \
1316   val = \
1317     min_range + \
1318   ((val + 1.0) / 2.0) * range; \
1319  \
1320   double draw_val = \
1321     ((val + 1.0) * GRID_HEIGHT) / 2.0; \
1322  \
1323   /* invert because higher Y means lower \
1324    * in cairo */ \
1325   draw_val = GRID_HEIGHT - draw_val; \
1326  \
1327   if (self->step_mode) \
1328     { \
1329       /* draw line */ \
1330       cairo_move_to ( \
1331         cr, \
1332         GRID_XSTART_GLOBAL + idouble, \
1333         GRID_YSTART_GLOBAL + GRID_HEIGHT); \
1334       cairo_line_to ( \
1335         cr, \
1336         GRID_XSTART_GLOBAL + idouble, \
1337         GRID_YSTART_GLOBAL + draw_val); \
1338       cairo_stroke (cr); \
1339     } \
1340   else if (i != 0) \
1341     { \
1342       /* draw line */ \
1343       cairo_move_to ( \
1344         cr, \
1345         GRID_XSTART_GLOBAL + i - 1, \
1346         GRID_YSTART_GLOBAL + \
1347           prev_draw_##val); \
1348       cairo_line_to ( \
1349         cr, \
1350         GRID_XSTART_GLOBAL + i, \
1351         GRID_YSTART_GLOBAL + \
1352           draw_val); \
1353       cairo_stroke (cr); \
1354     } \
1355   prev_draw_##val = draw_val
1356 
1357       double ratio = xvald / GRID_WIDTH;
1358 
1359       if (self->sine_on)
1360         {
1361           /* calculate sine */
1362           double sine =
1363             (double) self->sine_cache[xvall];
1364 
1365           DRAW_VAL (sine);
1366         }
1367       if (self->saw_on)
1368         {
1369           /* calculate saw */
1370           double saw =
1371             (double) self->saw_cache[xvall];
1372 
1373           DRAW_VAL (saw);
1374         }
1375       if (self->triangle_on)
1376         {
1377           double triangle;
1378           if (ratio > 0.4999)
1379             {
1380               triangle =
1381                 (1.0 - ratio) * 4.0 - 1.0;
1382             }
1383           else
1384             {
1385               triangle =
1386                 ratio * 4.0 - 1.0;
1387             }
1388 
1389           DRAW_VAL (triangle);
1390         }
1391       if (self->square_on)
1392         {
1393           double square;
1394           if (ratio > 0.4999)
1395             {
1396               square = - 1.0;
1397             }
1398           else
1399             {
1400               square = 1.0;
1401             }
1402 
1403           DRAW_VAL (square);
1404         }
1405       if (self->custom_on)
1406         {
1407           int prev_idx =
1408             get_prev_idx (
1409               node_indices, self->num_nodes,
1410               (float) ratio);
1411           int next_idx =
1412             get_next_idx (
1413               node_indices, self->num_nodes,
1414               (float) ratio);
1415 
1416           /* calculate custom */
1417           double custom =
1418             (double)
1419             get_custom_val_at_x (
1420               self->nodes[prev_idx][0],
1421               self->nodes[prev_idx][1],
1422               self->nodes[prev_idx][2],
1423               next_idx < 0 ? 1.f :
1424                 self->nodes[next_idx][0],
1425               next_idx < 0 ?
1426                 self->nodes[0][1] :
1427                 self->nodes[next_idx][1],
1428               next_idx < 0 ?
1429                 self->nodes[0][2] :
1430                 self->nodes[next_idx][2],
1431               (float) xvald, GRID_WIDTH);
1432 
1433           /* adjust for -1 to 1 */
1434           custom = custom * 2 - 1;
1435 
1436           DRAW_VAL (custom);
1437         }
1438 
1439       if (self->step_mode)
1440         {
1441           idouble += step_px;
1442           i = (int) idouble;
1443         }
1444       else
1445         {
1446           i++;
1447           idouble = (double) i;
1448         }
1449     }
1450 #undef DRAW_VAL
ExecHashIncreaseNumBuckets(HashJoinTable hashtable)1451 
1452   if (self->custom_on)
1453     {
1454       /* draw node curves */
1455       zlfo_ui_theme_set_cr_color (&self->ui_theme, cr, line);
1456       cairo_set_line_width (cr, 6);
1457       for (i = 0; i < self->num_nodes - 1; i++)
1458         {
1459           int index = node_indices[i].index;
1460           int next_index =
1461             node_indices[i + 1].index;
1462           ZtkWidget * nodew =
1463             self->node_widgets[index];
1464           ZtkWidget * next_nodew =
1465             self->node_widgets[next_index];
1466 
1467           cairo_move_to (
1468             cr,
1469             nodew->rect.x + nodew->rect.width / 2,
1470             nodew->rect.y + nodew->rect.height / 2);
1471           cairo_line_to (
1472             cr,
1473             next_nodew->rect.x +
1474               next_nodew->rect.width / 2,
1475             next_nodew->rect.y +
1476               next_nodew->rect.height / 2);
1477           cairo_stroke (cr);
1478         }
1479 
1480       /* draw line from last node to first node
1481        * reappearing at the end */
1482       ZtkWidget * first_nodew =
1483         self->node_widgets[0];
1484       ZtkWidget * last_nodew =
1485         self->node_widgets[
1486           node_indices[self->num_nodes - 1].index];
1487       ZtkRect rect = first_nodew->rect;
1488       rect.x = GRID_XEND_GLOBAL - rect.width / 2;
1489 
1490       /* draw line */
1491       cairo_move_to (
1492         cr,
1493         last_nodew->rect.x +
1494           last_nodew->rect.width / 2,
1495         last_nodew->rect.y +
1496           last_nodew->rect.height / 2);
1497       cairo_line_to (
1498         cr,
1499         rect.x + rect.width / 2,
1500         rect.y + rect.height / 2);
1501       cairo_stroke (cr);
1502 
1503       /* draw faded end node */
1504       zlfo_ui_theme_set_cr_color (&self->ui_theme, cr, selected_bg);
1505       cairo_arc (
1506         cr,
1507         rect.x + rect.width / 2,
1508         rect.y + rect.width / 2,
1509         rect.width / 2,
1510         0, 2 * G_PI);
1511       cairo_fill (cr);
1512       zlfo_ui_theme_set_cr_color (&self->ui_theme, cr, line);
1513       cairo_set_line_width (cr, 4);
1514       cairo_arc (
1515         cr,
1516         rect.x + rect.width / 2,
1517         rect.y + rect.width / 2,
1518         rect.width / 2,
1519         0, 2 * G_PI);
1520       cairo_stroke (cr);
1521     }
1522 }
1523 
1524 static void
1525 redraw_mid_region (
1526   LfoUi * self)
1527 {
1528 #if 0
1529   PuglRect rect;
1530   rect.x = GRID_XSTART_GLOBAL;
1531   rect.y = GRID_YSTART_GLOBAL;
1532   rect.width = GRID_WIDTH;
1533   rect.height = GRID_HEIGHT;
1534   puglPostRedisplayRect (
1535     self->app->view, rect);
1536 #endif
1537   puglPostRedisplay (
1538     self->app->view);
1539 }
1540 
1541 static void
1542 mid_region_bg_draw_cb (
1543   ZtkWidget * widget,
1544   cairo_t *   cr,
1545   ZtkRect *   draw_rect,
1546   LfoUi *    self)
1547 {
1548   if (self->has_change ||
1549       !ztk_rect_is_equal (
1550         draw_rect, &self->last_rect))
1551     {
1552       /*ztk_message ("change");*/
1553       ztk_rect_copy (
1554         &self->last_rect, draw_rect);
1555       z_cairo_reset_caches (
1556         &self->cached_cr,
1557         &self->cached_surface,
1558         (int) WIDTH,
1559         (int) HEIGHT, cr);
1560 
1561       /* set background */
1562       zlfo_ui_theme_set_cr_color (
1563         &self->ui_theme, self->cached_cr, selected_bg);
1564       cairo_rectangle (
1565         self->cached_cr, widget->rect.x, widget->rect.y,
1566         widget->rect.width, widget->rect.height);
1567       cairo_fill (self->cached_cr);
1568 
1569       float sync_rate_float =
1570         sync_rate_to_float (
1571           self->sync_rate,
1572           self->sync_rate_type);
1573 
1574       /**
1575        * Effective frequency.
1576        *
1577        * This is either the free-running frequency,
1578        * or the frequency corresponding to the current
1579        * sync rate.
1580        */
1581       float effective_freq =
1582         get_effective_freq (
1583           self->freerun, self->freq,
1584           &self->common.host_pos, sync_rate_float);
1585 
1586       /* calculate current sample */
1587       gint64 cur_time = g_get_monotonic_time ();
1588       if (self->last_current_sample_set == 0 ||
1589           (!self->freerun &&
1590               self->common.host_pos.speed < 0.001f))
1591         {
1592           self->last_current_sample_set = cur_time;
1593         }
1594       else
1595         {
1596           double samples_diff =
1597             ((double) GET_SAMPLERATE (self) *
1598              ((double)
1599                (cur_time -
1600                   self->last_current_sample_set) /
1601                1000000.0));
1602           self->current_sample += samples_diff;
1603           while (self->current_sample >=
1604                    (double) self->common.period_size)
1605             {
1606               self->current_sample -=
1607                 (double) self->common.period_size;
1608             }
1609           self->last_current_sample_set = cur_time;
1610         }
1611 
1612       /* draw grid */
ExecHashTableInsert(HashJoinTable hashtable,TupleTableSlot * slot,uint32 hashvalue)1613       for (int i = 0; i < 9; i++)
1614         {
1615           if ((i % 4) == 0)
1616             {
1617               zlfo_ui_theme_set_cr_color (
1618                 &self->ui_theme, self->cached_cr, grid_strong);
1619             }
1620           else
1621             {
1622               zlfo_ui_theme_set_cr_color (
1623                 &self->ui_theme, self->cached_cr, grid);
1624             }
1625           cairo_move_to (
1626             self->cached_cr,
1627             widget->rect.x + GRID_HPADDING +
1628               i * GRID_SPACE,
1629             widget->rect.y + GRID_YSTART_OFFSET);
1630           cairo_line_to (
1631             self->cached_cr,
1632             widget->rect.x + GRID_HPADDING +
1633               i * GRID_SPACE,
1634             widget->rect.y + GRID_YEND_OFFSET);
1635           cairo_stroke (self->cached_cr);
1636         }
1637       zlfo_ui_theme_set_cr_color (
1638         &self->ui_theme, self->cached_cr, grid_strong);
1639       cairo_move_to (
1640         self->cached_cr,
1641         GRID_XSTART_GLOBAL,
1642         widget->rect.y + 105);
1643       cairo_line_to (
1644         self->cached_cr,
1645         GRID_XEND_GLOBAL,
1646         widget->rect.y + 105);
1647       cairo_stroke (self->cached_cr);
1648 
1649       recalc_vars (
1650         self->freerun,
1651         &self->common.sine_multiplier,
1652         &self->common.saw_multiplier,
1653         &self->common.period_size,
1654         NULL,
1655         &self->common.host_pos, effective_freq,
1656         sync_rate_float,
1657         (float) GET_SAMPLERATE (self));
1658 
1659       /* draw other visible waves in the back */
1660       draw_graph (self, self->cached_cr);
1661     }
1662 
1663   cairo_set_source_surface (
1664     cr, self->cached_surface, 0, 0);
1665   cairo_paint (cr);
1666 
1667   /* draw current position */
1668   double current_offset =
1669     self->current_sample /
1670     (double) self->common.period_size;
1671 
1672   cairo_set_source_rgba (cr, 1, 1, 1, 1);
1673   cairo_move_to (
1674     cr,
1675     widget->rect.x + GRID_HPADDING +
1676       current_offset * GRID_WIDTH,
1677     widget->rect.y + GRID_YSTART_OFFSET);
1678   cairo_line_to (
1679     cr,
1680     widget->rect.x + GRID_HPADDING +
1681       current_offset * GRID_WIDTH,
1682     widget->rect.y + GRID_YEND_OFFSET);
1683   cairo_stroke (cr);
1684 
1685   self->has_change = 0;
1686 }
1687 
1688 static void
1689 mid_region_bg_update_cb (
1690   ZtkWidget * w,
1691   LfoUi *    self)
1692 {
1693   int double_click = 0;
1694   if (w->last_btn_press >
1695         w->before_last_btn_press &&
1696       !(math_doubles_equal (
1697           w->last_btn_press,
1698           self->last_double_click)))
1699     {
1700       double diff_sec =
1701         w->last_btn_press -
ExecParallelHashTableInsert(HashJoinTable hashtable,TupleTableSlot * slot,uint32 hashvalue)1702         w->before_last_btn_press;
1703 #if 0
1704       g_message (
1705         "last btn press %f last btn release %f "
1706         "(diff %f)", w->last_btn_press,
1707         w->last_btn_release, diff_sec);
1708 #endif
1709       double_click =
1710         diff_sec < DOUBLE_CLICK_INTERVAL &&
1711         diff_sec > 0.001;
1712       if (double_click)
1713         {
1714           self->last_double_click =
1715             w->last_btn_press;
1716         }
1717     }
1718 
1719   double dx = w->app->offset_press_x;
1720   double dy = w->app->offset_press_y;
1721   dx -= GRID_XSTART_GLOBAL;
1722   dy -= GRID_YSTART_GLOBAL;
1723 
1724   /* create new node */
1725   if (double_click && self->num_nodes < 16)
1726     {
1727       /* set next available node */
1728       node_pos_setter (
1729         self,
1730         (unsigned int) self->num_nodes,
1731         (float)
1732         CLAMP (dx / GRID_WIDTH, 0.0, 1.0));
1733       node_val_setter (
1734         self,
1735         (unsigned int) self->num_nodes,
1736         1.f -
1737           (float)
1738           CLAMP (dy / GRID_HEIGHT, 0.0, 1.0));
1739       self->dragging_node = self->num_nodes;
1740       num_nodes_setter (
1741         self, self->num_nodes + 1);
1742     }
1743   else if (w->state &
1744              ZTK_WIDGET_STATE_RIGHT_PRESSED)
1745     {
1746       self->dragging_node = -1;
1747     }
1748   else if (w->state & ZTK_WIDGET_STATE_PRESSED)
1749     {
1750       /* move currently dragged node */
1751       if (self->dragging_node >= 0)
1752         {
1753           node_pos_setter (
1754             self,
1755             (unsigned int) self->dragging_node,
1756             (float)
1757             CLAMP (dx / GRID_WIDTH, 0.0, 1.0));
1758           node_val_setter (
1759             self,
1760             (unsigned int) self->dragging_node,
1761             1.f -
1762               (float)
1763               CLAMP (dy / GRID_HEIGHT, 0.0, 1.0));
1764         }
1765     }
1766   else
ExecParallelHashTableInsertCurrentBatch(HashJoinTable hashtable,TupleTableSlot * slot,uint32 hashvalue)1767     {
1768       self->dragging_node = -1;
1769     }
1770 }
1771 
1772 static void
1773 add_mid_region_bg (
1774   LfoUi * self)
1775 {
1776   ZtkRect rect = {
1777     LEFT_BTN_WIDTH + 4,
1778     TOP_BTN_HEIGHT + 2,
1779     MID_REGION_WIDTH - 6, MID_REGION_HEIGHT };
1780   ZtkDrawingArea * da =
1781     ztk_drawing_area_new (
1782       &rect,
1783       (ZtkWidgetGenericCallback)
1784       mid_region_bg_update_cb,
1785       (ZtkWidgetDrawCallback) mid_region_bg_draw_cb,
1786       NULL, self);
1787   self->mid_region = (ZtkWidget *) da;
1788   ztk_app_add_widget (
1789     self->app, (ZtkWidget *) da, 0);
1790 }
1791 
1792 typedef struct NodeData
1793 {
1794   /** Node index. */
1795   int idx;
1796 
1797   LfoUi *     zlfo_ui;
1798 } NodeData;
1799 
1800 static void
1801 node_update_cb (
1802   ZtkWidget * w,
1803   NodeData *  data)
1804 {
1805   LfoUi * self = data->zlfo_ui;
1806 
1807   /* set visibility */
1808   if (data->idx < self->num_nodes)
1809     {
1810       ztk_widget_set_visible (w, 1);
ExecHashGetHashValue(HashJoinTable hashtable,ExprContext * econtext,List * hashkeys,bool outer_tuple,bool keep_nulls,uint32 * hashvalue)1811     }
1812   else
1813     {
1814       ztk_widget_set_visible (w, 0);
1815       return;
1816     }
1817 
1818   /* delete if right clicked */
1819   if (w->state & ZTK_WIDGET_STATE_RIGHT_PRESSED)
1820     {
1821       if (data->idx != 0 &&
1822           !math_doubles_equal (
1823             self->last_delete_click,
1824             w->last_btn_press))
1825         {
1826           for (int i = data->idx; i < 15; i++)
1827             {
1828               self->nodes[i][0] =
1829                 self->nodes[i + 1][0];
1830               self->nodes[i][1] =
1831                 self->nodes[i + 1][1];
1832               node_pos_setter (
1833                 self, (unsigned int) i,
1834                 self->nodes[i + 1][0]);
1835               node_val_setter (
1836                 self, (unsigned int) i,
1837                 self->nodes[i + 1][1]);
1838             }
1839           self->num_nodes--;
1840           num_nodes_setter (
1841             self, self->num_nodes);
1842           self->last_delete_click =
1843             w->last_btn_press;
1844         }
1845     }
1846   /* move if dragged */
1847   else if (w->state & ZTK_WIDGET_STATE_PRESSED)
1848     {
1849       double dx = w->app->offset_press_x;
1850       double dy = w->app->offset_press_y;
1851       dx -= GRID_XSTART_GLOBAL;
1852       dy -= GRID_YSTART_GLOBAL;
1853 
1854       /* first and node should not be movable */
1855       if (data->idx != 0)
1856         {
1857           node_pos_setter (
1858             self, (unsigned int) data->idx,
1859             (float)
1860             CLAMP (dx / GRID_WIDTH, 0.0, 1.0));
1861         }
1862       node_val_setter (
1863         self, (unsigned int) data->idx,
1864         1.f -
1865           (float)
1866           CLAMP (dy / GRID_HEIGHT, 0.0, 1.0));
1867     }
1868 
1869   double width = NODE_WIDTH;
1870   double x_offset =
1871     (double) self->nodes[data->idx][0];
1872   double y_offset =
1873     1.0 - (double) self->nodes[data->idx][1];
1874   double total_height = GRID_HEIGHT;
1875 
1876   /* set rectangle */
1877   ZtkRect rect = {
1878     (GRID_XSTART_GLOBAL +
1879       x_offset * GRID_WIDTH) - width / 2,
1880     (GRID_YSTART_GLOBAL +
1881       y_offset * total_height) - width / 2,
1882     width, width };
1883   w->rect = rect;
1884 }
1885 
1886 static void
1887 node_draw_cb (
1888   ZtkWidget * w,
1889   cairo_t *   cr,
1890   ZtkRect *   draw_rect,
1891   NodeData *  data)
1892 {
1893   LfoUi * self = data->zlfo_ui;
1894 
1895   if (!self->custom_on)
1896     return;
1897 
1898   double width = NODE_WIDTH;
1899 
1900   zlfo_ui_theme_set_cr_color (&self->ui_theme, cr, grid_strong);
1901   cairo_arc (
1902     cr,
1903     w->rect.x + width / 2,
1904     w->rect.y + width / 2,
1905     width / 2,
1906     0, 2 * G_PI);
1907   cairo_fill (cr);
1908 
1909   zlfo_ui_theme_set_cr_color (&self->ui_theme, cr, line);
1910   cairo_set_line_width (cr, 4);
1911   cairo_arc (
1912     cr,
1913     w->rect.x + width / 2,
1914     w->rect.y + width / 2,
1915     width / 2,
1916     0, 2 * G_PI);
1917   cairo_stroke (cr);
1918 }
ExecHashGetBucketAndBatch(HashJoinTable hashtable,uint32 hashvalue,int * bucketno,int * batchno)1919 
1920 static void
1921 add_nodes (
1922   LfoUi * self)
1923 {
1924   for (int i = 0; i < 16; i++)
1925     {
1926       ZtkRect rect = {
1927         0, 0, 0, 0 };
1928       NodeData * data =
1929         calloc (1, sizeof (NodeData));
1930       data->idx = i;
1931       data->zlfo_ui = self;
1932       ZtkDrawingArea * da =
1933         ztk_drawing_area_new (
1934           &rect,
1935           (ZtkWidgetGenericCallback)
1936           node_update_cb,
1937           (ZtkWidgetDrawCallback)
1938           node_draw_cb,
1939           NULL, data);
1940       ZtkWidget * w = (ZtkWidget *) da;
1941       ztk_widget_set_visible (w, 0);
1942       self->node_widgets[i] = w;
1943       ztk_app_add_widget (
1944         self->app, w,
1945         /* nodes on the left should be on top */
1946         2 + (15 - i));
1947     }
1948 }
1949 
1950 static void
ExecScanHashBucket(HashJoinState * hjstate,ExprContext * econtext)1951 range_draw_cb (
1952   ZtkWidget * widget,
1953   cairo_t *   cr,
1954   ZtkRect *   draw_rect,
1955   LfoUi *    self)
1956 {
1957   /* draw bg svg */
1958   ZtkRect rect = {
1959     widget->rect.x,
1960     widget->rect.y,
1961     widget->rect.width,
1962     widget->rect.height };
1963   ztk_rsvg_draw (
1964     self->ui_theme.range_svg, cr, &rect);
1965 
1966   /* draw range */
1967   double width = RANGE_POINT_WIDTH;
1968   double start_x = RANGE_STARTX - width / 2;
1969   double start_y = 83 - width / 2;
1970   double range_height = RANGE_HEIGHT;
1971   zlfo_ui_theme_set_cr_color (
1972     &self->ui_theme, cr, button_click);
1973 
1974   /* range */
1975   double range_min_y_normalized =
1976     1.0 - ((double) self->range_min + 1.0) / 2.0;
1977   double range_max_y_normalized =
1978     1.0 - ((double) self->range_max + 1.0) / 2.0;
1979   cairo_set_line_width (cr, 4.0);
1980   cairo_move_to (
1981     cr, start_x + width / 2,
1982     start_y + range_max_y_normalized *
1983       range_height + width / 2);
1984   cairo_line_to (
1985     cr, start_x + width / 2,
1986     start_y + range_min_y_normalized *
1987       range_height + width / 2);
1988   cairo_stroke (cr);
1989 }
1990 
1991 typedef struct RangePointData
1992 {
1993   /** 1 if min, 0 if max. */
1994   int is_min;
1995 
1996   ZtkDrawingArea * da;
1997 
1998   LfoUi * zlfo_ui;
1999 } RangePointData;
2000 
2001 static void
2002 range_point_update_cb (
2003   ZtkWidget * w,
2004   RangePointData * data)
2005 {
2006   LfoUi * self = data->zlfo_ui;
2007 
2008   /* get min/max coordinates */
2009   double min_y =
2010     (1.0 - ((-1.0 +
2011       1.0) / 2.0)) * RANGE_HEIGHT +
ExecParallelScanHashBucket(HashJoinState * hjstate,ExprContext * econtext)2012      83 - RANGE_POINT_WIDTH / 2;
2013   double max_y =
2014     (1.0 - ((1.0 +
2015       1.0) / 2.0)) * RANGE_HEIGHT +
2016      83 - RANGE_POINT_WIDTH / 2;
2017   (void) min_y;
2018 
2019   if (w->state & ZTK_WIDGET_STATE_PRESSED)
2020     {
2021       double dy = w->app->offset_press_y;
2022       dy -= (max_y + w->rect.height / 2.0);
2023       dy = dy / RANGE_HEIGHT;
2024       dy = CLAMP (dy, 0.0, 1.0);
2025       dy = 1.0 - dy;
2026 
2027       /* at this point, dy is 1 at the top and 0
2028        * at the bot */
2029 
2030       dy = dy * 2.0 - 1.0;
2031 
2032       if (data->is_min)
2033         {
2034           range_min_setter (
2035             NULL, self, (float) dy);
2036         }
2037       else
2038         {
2039           range_max_setter (
2040             NULL, self, (float) dy);
2041         }
2042     }
2043 
2044   (void) range_min_getter;
2045   (void) range_max_getter;
2046 
2047   /* update position */
2048   w->rect.y =
2049     (1.0 - (((double)
2050       (data->is_min ?
2051          self->range_min : self->range_max) +
2052       1.0) / 2.0)) * RANGE_HEIGHT +
2053      83 - RANGE_POINT_WIDTH / 2;
2054 }
2055 
2056 static void
2057 range_point_draw_cb (
2058   ZtkWidget * widget,
2059   cairo_t *   cr,
2060   ZtkRect *   draw_rect,
2061   RangePointData * data)
2062 {
ExecPrepHashTableForUnmatched(HashJoinState * hjstate)2063   LfoUi * self = data->zlfo_ui;
2064 
2065   double width = RANGE_POINT_WIDTH;
2066   double start_x = widget->rect.x;
2067   double start_y = widget->rect.y;
2068 
2069   zlfo_ui_theme_set_cr_color (
2070     &self->ui_theme, cr, button_click);
2071   cairo_arc (
2072     cr, start_x + width / 2, start_y + width / 2,
2073     width / 2,
2074     0, 2 * G_PI);
2075   cairo_fill (cr);
2076 }
2077 
2078 static void
2079 add_range (
2080   LfoUi * self)
2081 {
2082   /* add min point */
2083   double point_start_x =
2084     RANGE_STARTX - RANGE_POINT_WIDTH / 2;
2085   double point_start_y = 83 - RANGE_POINT_WIDTH / 2;
2086   ZtkRect rect = {
ExecScanHashTableForUnmatched(HashJoinState * hjstate,ExprContext * econtext)2087     point_start_x, point_start_y,
2088     RANGE_POINT_WIDTH, RANGE_POINT_WIDTH };
2089   RangePointData * rp =
2090     calloc (1, sizeof (RangePointData));
2091   rp->is_min = 1;
2092   rp->zlfo_ui = self;
2093   ZtkDrawingArea * da =
2094     ztk_drawing_area_new (
2095       &rect,
2096       (ZtkWidgetGenericCallback)
2097       range_point_update_cb,
2098       (ZtkWidgetDrawCallback) range_point_draw_cb,
2099       NULL, rp);
2100   rp->da = da;
2101   ztk_app_add_widget (
2102     self->app, (ZtkWidget *) da, 2);
2103 
2104   /* add max point */
2105   rp = calloc (1, sizeof (RangePointData));
2106   rp->is_min = 0;
2107   rp->zlfo_ui = self;
2108   da =
2109     ztk_drawing_area_new (
2110       &rect,
2111       (ZtkWidgetGenericCallback)
2112       range_point_update_cb,
2113       (ZtkWidgetDrawCallback) range_point_draw_cb,
2114       NULL, rp);
2115   rp->da = da;
2116   ztk_app_add_widget (
2117     self->app, (ZtkWidget *) da, 2);
2118 
2119   /* add line */
2120   rect.x =
2121     (LEFT_BTN_WIDTH + MID_REGION_WIDTH) - 10;
2122   rect.y = 58;
2123   rect.width = 64;
2124   rect.height = 180;
2125   da =
2126     ztk_drawing_area_new (
2127       &rect, NULL,
2128       (ZtkWidgetDrawCallback) range_draw_cb,
2129       NULL, self);
2130   ztk_app_add_widget (
2131     self->app, (ZtkWidget *) da, 0);
2132 }
2133 
2134 #if 0
2135 static void
2136 zrythm_icon_draw_cb (
2137   ZtkWidget * widget,
2138   cairo_t *   cr,
2139   ZtkRect *   draw_rect,
2140   LfoUi *    self)
2141 {
2142   ZtkRect rect = {
2143     widget->rect.x,
2144     widget->rect.y,
2145     widget->rect.width,
2146     widget->rect.height };
2147   ZtkWidgetState state = widget->state;
2148   if (state & ZTK_WIDGET_STATE_PRESSED)
2149     {
2150       zlfo_ui_theme_set_cr_color (cr, button_hover);
2151       ztk_rsvg_draw (
2152         zlfo_ui_theme.zrythm_orange_svg, cr, &rect);
2153     }
2154   else if (state & ZTK_WIDGET_STATE_HOVERED)
2155     {
2156       zlfo_ui_theme_set_cr_color (cr, button_hover);
2157       ztk_rsvg_draw (
ExecHashTableReset(HashJoinTable hashtable)2158         zlfo_ui_theme.zrythm_hover_svg, cr, &rect);
2159     }
2160   else
2161     {
2162       zlfo_ui_theme_set_cr_color (
2163         cr, button_normal);
2164       ztk_rsvg_draw (
2165         zlfo_ui_theme.zrythm_svg, cr, &rect);
2166     }
2167 }
2168 #endif
2169 
2170 static void
2171 on_zrythm_btn_clicked (
2172   ZtkWidget * widget,
2173   LfoUi *    self)
2174 {
2175   const double width = 378;
2176   const double height = 180;
2177   ZtkRect full_rect = {
2178     0, 0, WIDTH, HEIGHT };
2179   ZtkRect rect = {
2180     WIDTH / 2 - width / 2,
2181     HEIGHT / 2 - height / 2,
2182     width, height };
2183   ZtkDialog * dialog =
2184     ztk_dialog_new (
2185       widget->app, &full_rect, &rect, self);
2186   ztk_dialog_make_about (
ExecHashTableResetMatchFlags(HashJoinTable hashtable)2187     dialog, "LFO",
2188     "v" LFO_VERSION,
2189     "Copyright (C) 2020 Alexandros Theodotou",
2190     ZTK_DIALOG_ABOUT_LICENSE_AGPL_3_PLUS,
2191     "Design by Mire");
2192   ztk_app_add_widget (
2193     self->app, (ZtkWidget *) dialog, 400);
2194 }
2195 
2196 static void
2197 add_zrythm_icon (
2198   LfoUi * self)
2199 {
2200   ZtkRect rect = {
2201     LEFT_BTN_WIDTH + MID_REGION_WIDTH + 8,
2202     6, 30, 30 };
2203   ZtkButton * btn =
2204     ztk_button_new (
2205       &rect,
2206       (ZtkWidgetActivateCallback)
2207       on_zrythm_btn_clicked, self);
2208   ztk_button_make_svged (
2209     btn, 0, 0,
2210     self->ui_theme.zrythm_svg,
2211     self->ui_theme.zrythm_hover_svg,
2212     self->ui_theme.zrythm_orange_svg);
ExecReScanHash(HashState * node)2213   ztk_app_add_widget (
2214     self->app, (ZtkWidget *) btn, 0);
2215 }
2216 
2217 /**
2218  * Macro to get real value.
2219  */
2220 #define GET_REAL_VAL \
2221   ((*ctrl->getter) (ctrl, ctrl->object))
2222 
2223 /**
2224  * MAcro to get real value from knob value.
2225  */
2226 #define REAL_VAL_FROM_KNOB(knob) \
2227   (ctrl->min + (float) knob * \
2228    (ctrl->max - ctrl->min))
2229 
2230 /**
2231  * Converts from real value to knob value
2232  */
ExecHashBuildSkewHash(HashJoinTable hashtable,Hash * node,int mcvsToUse)2233 #define KNOB_VAL_FROM_REAL(real) \
2234   (((float) real - ctrl->min) / \
2235    (ctrl->max - ctrl->min))
2236 
2237 /**
2238  * Sets real val
2239  */
2240 #define SET_REAL_VAL(real) \
2241    ((*ctrl->setter)(ctrl->object, (float) real))
2242 
2243 static void
2244 shift_control_draw_cb (
2245   ZtkWidget * widget,
2246   cairo_t *   cr,
2247   ZtkRect *   draw_rect,
2248   LfoUi *    self)
2249 {
2250   ZtkControl * ctrl = (ZtkControl *) widget;
2251 
2252   /* draw bg */
2253   zlfo_ui_theme_set_cr_color (
2254     &self->ui_theme, cr, button_normal);
2255   cairo_rectangle (
2256     cr, widget->rect.x, widget->rect.y,
2257     widget->rect.width, widget->rect.height);
2258   cairo_fill (cr);
2259 
2260   /* draw black bg */
2261   const int bg_padding = 2;
2262   zlfo_ui_theme_set_cr_color (&self->ui_theme, cr, bg);
2263   cairo_rectangle (
2264     cr, widget->rect.x + bg_padding,
2265     widget->rect.y + bg_padding,
2266     widget->rect.width - bg_padding * 2,
2267     widget->rect.height - bg_padding * 2);
2268   cairo_fill (cr);
2269 
2270   /* set color */
2271   if (widget->state & ZTK_WIDGET_STATE_PRESSED)
2272     {
2273       cairo_set_source_rgba (cr, 0.9, 0.9, 0.9, 1);
2274     }
2275   else if (widget->state & ZTK_WIDGET_STATE_HOVERED)
2276     {
2277       cairo_set_source_rgba (cr, 0.8, 0.8, 0.8, 1);
2278     }
2279   else
2280     {
2281       cairo_set_source_rgba (cr, 0.7, 0.7, 0.7, 1);
2282     }
2283 
2284   /* the half width of the available bar area */
2285   double half_width =
2286     (widget->rect.width  - bg_padding * 2.0) / 2.0;
2287 
2288   double handle_size = 12.0;
2289 
2290   /* draw bar */
2291   double real_val = (double) GET_REAL_VAL;
2292   if (real_val < 0.5)
2293     {
2294       double work_val = real_val / 0.5;
2295       double start_x =
2296         work_val * half_width - handle_size / 2.0;
2297       cairo_rectangle (
2298         cr,
2299         widget->rect.x + bg_padding +
2300           (start_x < 0.0 ? 0.0 : start_x),
2301         widget->rect.y + bg_padding,
2302         start_x < 0.0 ?
2303           handle_size + start_x : handle_size,
2304         widget->rect.height - bg_padding * 2);
2305     }
2306   else
2307     {
2308       double work_val = (real_val - 0.5) / 0.5;
2309       double start_x =
2310         widget->rect.x + bg_padding + half_width +
2311         (work_val * half_width - handle_size / 2.0);
2312       double extrusion =
2313         (start_x + handle_size) -
2314           ((widget->rect.x + widget->rect.width) - bg_padding);
2315       cairo_rectangle (
2316         cr, start_x,
2317         widget->rect.y + bg_padding,
2318         extrusion > 0.0 ?
2319           handle_size - extrusion : handle_size,
2320         widget->rect.height - bg_padding * 2);
2321     }
2322   cairo_fill (cr);
2323 }
2324 
2325 static void
2326 grid_lbl_draw_cb (
2327   ZtkWidget * widget,
2328   cairo_t *   cr,
2329   ZtkRect *   draw_rect,
2330   DrawData *  data)
2331 {
2332   LfoUi * self = data->zlfo_ui;
2333 
2334   /* draw svgs */
2335 #define DRAW_SVG(caps,lowercase) \
2336   case LBL_TYPE_##caps: \
2337     { \
2338       ZtkRect rect = { \
2339         widget->rect.x, \
2340         widget->rect.y, \
2341         widget->rect.width, \
2342         widget->rect.height }; \
2343       ztk_rsvg_draw ( \
2344         self->ui_theme.lowercase##_svg, \
2345         cr, &rect); \
2346     } \
2347     break
2348 
2349   switch (data->val)
2350     {
2351       DRAW_SVG (INVERT, invert);
2352       DRAW_SVG (SHIFT, shift);
2353     default:
2354       break;
2355     }
2356 
2357 #undef DRAW_SVG
2358 }
2359 
2360 static void
2361 add_grid_controls (
2362   LfoUi * self)
2363 {
2364   int padding = 2;
2365   int width = 76;
2366   int height = 22;
2367   int start = LEFT_BTN_WIDTH + padding + 12;
2368   for (int i = 0; i < NUM_GRID_BUTTONS; i++)
2369     {
2370       ZtkRect rect;
2371       switch (i)
2372         {
2373         case GRID_BTN_SNAP:
2374           rect.x =
2375             start + padding;
2376           rect.width = 76;
2377           break;
2378         case GRID_BTN_HMIRROR:
2379           rect.x =
2380             start + padding + width + padding +
2381             69;
2382           rect.width = 40;
2383           break;
2384         case GRID_BTN_VMIRROR:
2385           rect.x =
ExecHashGetSkewBucket(HashJoinTable hashtable,uint32 hashvalue)2386             start + padding + width + padding +
2387             111;
2388           rect.width = 40;
2389           break;
2390         default:
2391           break;
2392         }
2393       rect.height = 22;
2394       rect.y = TOP_BTN_HEIGHT + 12;
2395       DrawData * data =
2396         calloc (1, sizeof (DrawData));
2397       data->val = i;
2398       data->type = DATA_TYPE_BTN_GRID;
2399       data->zlfo_ui = self;
2400       ZtkButton * btn =
2401         ztk_button_new (
2402           &rect,
2403           (ZtkWidgetActivateCallback)
2404           on_btn_clicked, data);
2405       ztk_button_set_background_colors (
2406         btn,
2407         &self->ui_theme.bg,
2408         &self->ui_theme.button_hover,
2409         &self->ui_theme.left_button_click);
2410       switch (i)
2411         {
2412         case GRID_BTN_HMIRROR:
2413           ztk_button_make_svged (
2414             btn, 0, 0,
2415             self->ui_theme.hmirror_svg,
2416             self->ui_theme.hmirror_hover_svg,
2417             self->ui_theme.hmirror_click_svg);
2418           break;
2419         case GRID_BTN_VMIRROR:
2420           ztk_button_make_svged (
2421             btn, 0, 0,
2422             self->ui_theme.vmirror_svg,
2423             self->ui_theme.vmirror_hover_svg,
2424             self->ui_theme.vmirror_click_svg);
2425           break;
2426         case GRID_BTN_SNAP:
2427           ztk_button_make_svged (
2428             btn, 0, 0,
2429             self->ui_theme.grid_snap_svg,
2430             self->ui_theme.grid_snap_hover_svg,
2431             self->ui_theme.grid_snap_click_svg);
ExecHashSkewTableInsert(HashJoinTable hashtable,TupleTableSlot * slot,uint32 hashvalue,int bucketNumber)2432           break;
2433         }
2434       ztk_button_make_toggled (
2435         btn,
2436         (ZtkButtonToggledGetter)
2437         get_button_active);
2438       ztk_app_add_widget (
2439         self->app, (ZtkWidget *) btn, 4);
2440     }
2441 
2442   /* add shift control */
2443   ZtkRect rect = {
2444     start + padding + width + padding + 210,
2445     TOP_BTN_HEIGHT + 12, 76, 22 };
2446   ZtkControl * control =
2447     ztk_control_new (
2448       &rect,
2449       (ZtkControlGetter) shift_getter,
2450       (ZtkControlSetter) shift_setter,
2451       (ZtkWidgetDrawCallback) shift_control_draw_cb,
2452       ZTK_CTRL_DRAG_HORIZONTAL,
2453       self, 0.f, 1.f, 0.5f);
2454   control->sensitivity = 0.02f;
2455   ZtkWidget * control_widget = (ZtkWidget *) control;
2456   control_widget->user_data = self;
2457   ztk_control_set_relative_mode (control, 0);
2458   ztk_app_add_widget (
2459     self->app, (ZtkWidget *) control, 4);
2460 
2461   /* add labels */
2462   padding = 2;
2463   width = 76;
2464   height = 22;
2465   start = LEFT_BTN_WIDTH + padding;
2466   for (int i = 0; i < NUM_LBL_TYPES; i++)
2467     {
2468       if (i == LBL_TYPE_INVERT)
2469         {
2470           rect.x = 138;
2471         }
2472       else if (i == LBL_TYPE_SHIFT)
2473         {
2474           rect.x = 282;
2475         }
2476       rect.y = TOP_BTN_HEIGHT + 12;
2477       rect.width = width;
ExecHashRemoveNextSkewBucket(HashJoinTable hashtable)2478       rect.height = height;
2479 
2480       DrawData * data =
2481         calloc (1, sizeof (DrawData));
2482       data->val = i;
2483       data->type = DATA_TYPE_LBL;
2484       data->zlfo_ui = self;
2485       ZtkDrawingArea * da =
2486         ztk_drawing_area_new (
2487           &rect, NULL,
2488           (ZtkWidgetDrawCallback) grid_lbl_draw_cb,
2489           NULL, data);
2490       ztk_app_add_widget (
2491         self->app, (ZtkWidget *) da, 1);
2492     }
2493 }
2494 
2495 static void
2496 create_ui (
2497   LfoUi * self)
2498 {
2499   /* resize the host's window. */
2500   self->resize->ui_resize (
2501     self->resize->handle, WIDTH, HEIGHT);
2502 
2503   self->app = ztk_app_new (
2504     PLUGIN_NAME, self->parent_window,
2505     WIDTH, HEIGHT);
2506 
2507   /* init the theme */
2508   int ret =
2509     zlfo_ui_theme_init (
2510       &self->ui_theme,
2511       &self->common.pl_common.logger,
2512       self->bundle_path);
2513   if (ret)
2514     {
2515       return;
2516     }
2517 
2518   /** add each control */
2519   add_bg_widget (self);
2520   add_left_buttons (self);
2521   add_top_buttons (self);
2522   add_bot_buttons (self);
2523   add_mid_region_bg (self);
2524   add_nodes (self);
2525   add_grid_controls (self);
2526   add_range (self);
2527   add_zrythm_icon (self);
2528 }
2529 
2530 static LV2UI_Handle
2531 instantiate (
2532   const LV2UI_Descriptor*   descriptor,
2533   const char*               plugin_uri,
2534   const char*               bundle_path,
2535   LV2UI_Write_Function      write_function,
2536   LV2UI_Controller          controller,
2537   LV2UI_Widget*             widget,
2538   const LV2_Feature* const* features)
2539 {
2540   LfoUi * self = calloc (1, sizeof (LfoUi));
2541   self->write = write_function;
2542   self->controller = controller;
2543   self->dragging_node = -1;
2544   self->has_change = 1;
2545   strcpy (self->bundle_path, bundle_path);
2546 
2547   PluginCommon * pl_common = &self->common.pl_common;
2548 
2549 #ifndef RELEASE
2550   ztk_log_set_level (ZTK_LOG_LEVEL_DEBUG);
2551 #endif
2552 
2553 #define HAVE_FEATURE(x) \
2554   (!strcmp(features[i]->URI, x))
2555 
2556   for (int i = 0; features[i]; ++i)
2557     {
2558       if (HAVE_FEATURE (LV2_UI__parent))
2559         {
2560           self->parent_window = features[i]->data;
2561         }
2562       else if (HAVE_FEATURE (LV2_UI__resize))
2563         {
2564           self->resize =
2565             (LV2UI_Resize*)features[i]->data;
2566         }
2567       else if (HAVE_FEATURE (LV2_URID__map))
2568         {
2569           pl_common->map =
2570             (LV2_URID_Map *) features[i]->data;
2571         }
2572       else if (HAVE_FEATURE (LV2_LOG__log))
2573         {
2574           pl_common->log =
2575             (LV2_Log_Log *) features[i]->data;
2576         }
2577     }
2578 
2579 #undef HAVE_FEATURE
2580 
2581   if (!pl_common->map)
2582     {
2583       lv2_log_error (
2584         &pl_common->logger, "Missing feature urid:map\n");
2585     }
2586 
2587   /* map uris */
2588   map_uris (pl_common->map, &self->common);
2589 
2590   lv2_atom_forge_init (
ExecHashEstimate(HashState * node,ParallelContext * pcxt)2591     &pl_common->forge, pl_common->map);
2592 
2593   /* create UI and set the native window to the
2594    * widget */
2595   create_ui (self);
2596   *widget =
2597     (LV2UI_Widget)
2598     puglGetNativeWindow (self->app->view);
2599 
2600   /* let the plugin know that the UI is active */
2601   uint8_t obj_buf[64];
2602   lv2_atom_forge_set_buffer (
2603     &pl_common->forge, obj_buf, 64);
2604   LV2_Atom_Forge_Frame frame;
2605   lv2_atom_forge_frame_time (
2606     &pl_common->forge, 0);
2607   LV2_Atom* msg =
2608     (LV2_Atom *)
2609     lv2_atom_forge_object (
ExecHashInitializeDSM(HashState * node,ParallelContext * pcxt)2610       &pl_common->forge, &frame, 1,
2611       self->common.uris.ui_on);
2612   lv2_atom_forge_pop (&pl_common->forge, &frame);
2613   self->write (
2614     self->controller, 0,
2615     lv2_atom_total_size (msg),
2616     pl_common->uris.atom_eventTransfer, msg);
2617 
2618   return self;
2619 }
2620 
2621 static void
2622 cleanup (LV2UI_Handle handle)
2623 {
2624   LfoUi * self = (LfoUi *) handle;
2625   PluginCommon * pl_common = &self->common.pl_common;
2626 
2627   /* let the plugin know that the UI is off */
2628   uint8_t obj_buf[64];
2629   lv2_atom_forge_set_buffer (
2630     &pl_common->forge, obj_buf, 64);
2631   LV2_Atom_Forge_Frame frame;
2632   lv2_atom_forge_frame_time (
2633     &pl_common->forge, 0);
2634   LV2_Atom* msg =
ExecHashInitializeWorker(HashState * node,ParallelWorkerContext * pwcxt)2635     (LV2_Atom *)
2636     lv2_atom_forge_object (
2637       &pl_common->forge, &frame, 1,
2638       self->common.uris.ui_off);
2639   lv2_atom_forge_pop (&pl_common->forge, &frame);
2640   self->write (
2641     self->controller, 0,
2642     lv2_atom_total_size (msg),
2643     pl_common->uris.atom_eventTransfer, msg);
2644 
2645   ztk_app_free (self->app);
2646 
2647   free (self);
2648 }
2649 
2650 /**
2651  * Port event from the plugin.
2652  */
2653 static void
2654 port_event (
2655   LV2UI_Handle handle,
2656   uint32_t     port_index,
2657   uint32_t     buffer_size,
2658   uint32_t     format,
2659   const void*  buffer)
2660 {
ExecShutdownHash(HashState * node)2661   LfoUi * self = (LfoUi *) handle;
2662   PluginCommon * pl_common = &self->common.pl_common;
2663 
2664   /* check type of data received
2665    *  format == 0: [float] control-port event
2666    *  format > 0: message
2667    *  Every event message is sent as separate
2668    *  port-event
2669    */
2670   if (format == 0)
2671     {
2672       switch (port_index)
2673         {
2674         case LFO_FREQ:
2675           self->freq = * (const float *) buffer;
2676           break;
ExecHashRetrieveInstrumentation(HashState * node)2677         case LFO_CV_GATE:
2678           self->cv_gate =
2679             * (const float *) buffer;
2680           break;
2681         case LFO_CV_TRIGGER:
2682           self->cv_trigger =
2683             * (const float *) buffer;
2684           break;
2685         case LFO_GATE:
2686           self->gate =
2687             * (const float *) buffer;
2688           break;
2689         case LFO_TRIGGER:
2690           self->trigger =
2691             (int) * (const float *) buffer;
2692           break;
2693         case LFO_SHIFT:
2694           self->shift = * (const float *) buffer;
2695           break;
2696         case LFO_RANGE_MIN:
2697           self->range_min =
2698             * (const float *) buffer;
2699           break;
2700         case LFO_RANGE_MAX:
2701           self->range_max =
2702             * (const float *) buffer;
2703           break;
2704         case LFO_STEP_MODE:
2705           self->step_mode =
2706             (int) * (const float *) buffer;
2707           break;
ExecHashAccumInstrumentation(HashInstrumentation * instrument,HashJoinTable hashtable)2708         case LFO_FREE_RUNNING:
2709           self->freerun =
2710             (int) * (const float *) buffer;
2711           break;
2712         case LFO_GRID_STEP:
2713           self->grid_step =
2714             (int) * (const float *) buffer;
2715           break;
2716         case LFO_SYNC_RATE:
2717           self->sync_rate =
2718             * (const float *) buffer;
2719           break;
2720         case LFO_SYNC_RATE_TYPE:
2721           self->sync_rate_type =
2722             * (const float *) buffer;
2723           break;
2724         case LFO_HINVERT:
2725           self->hinvert =
2726             (int) * (const float *) buffer;
dense_alloc(HashJoinTable hashtable,Size size)2727           break;
2728         case LFO_VINVERT:
2729           self->vinvert =
2730             (int) * (const float *) buffer;
2731           break;
2732         case LFO_SINE_TOGGLE:
2733           self->sine_on =
2734             (int) * (const float *) buffer;
2735           break;
2736         case LFO_SAW_TOGGLE:
2737           self->saw_on =
2738             (int) * (const float *) buffer;
2739           break;
2740         case LFO_SQUARE_TOGGLE:
2741           self->square_on =
2742             (int) * (const float *) buffer;
2743           break;
2744         case LFO_TRIANGLE_TOGGLE:
2745           self->triangle_on =
2746             (int) * (const float *) buffer;
2747           break;
2748         case LFO_CUSTOM_TOGGLE:
2749           self->custom_on =
2750             (int) * (const float *) buffer;
2751           break;
2752         case LFO_NUM_NODES:
2753           self->num_nodes =
2754             (int) * (const float *) buffer;
2755           break;
2756         case LFO_SAMPLE_TO_UI:
2757           self->current_sample =
2758             (double) * (const float *) buffer;
2759           break;
2760         default:
2761           break;
2762         }
2763 
2764       if (port_index >= LFO_NODE_1_POS &&
2765           port_index <= LFO_NODE_16_CURVE)
2766         {
2767           unsigned int prop =
2768             (port_index - LFO_NODE_1_POS) % 3;
2769           unsigned int node_id =
2770             (port_index - LFO_NODE_1_POS) / 3;
2771           self->nodes[node_id][prop] =
2772             * (const float *) buffer;
2773         }
2774       /*puglPostRedisplay (self->app->view);*/
2775 
2776       if (port_index != LFO_SAMPLE_TO_UI)
2777         {
2778           self->has_change = 1;
2779         }
2780     }
2781   else if (format ==
2782              pl_common->uris.atom_eventTransfer)
2783     {
2784       const LV2_Atom* atom =
2785         (const LV2_Atom*) buffer;
2786       if (lv2_atom_forge_is_object_type (
2787             &pl_common->forge, atom->type))
2788         {
2789           const LV2_Atom_Object* obj =
2790             (const LV2_Atom_Object*) atom;
2791           if (obj->body.otype ==
2792                 self->common.uris.ui_state)
2793             {
2794               const LV2_Atom
2795                 * current_sample = NULL,
2796                 * samplerate = NULL,
2797                 * period_size = NULL,
2798                 * sine_multiplier = NULL,
2799                 * saw_multiplier = NULL;
2800               lv2_atom_object_get (
2801                 obj,
2802                 self->common.uris.
2803                   ui_state_current_sample,
2804                 &current_sample,
2805                 self->common.uris.
2806                   ui_state_period_size,
ExecParallelHashTupleAlloc(HashJoinTable hashtable,size_t size,dsa_pointer * shared)2807                 &period_size,
2808                 self->common.uris.
2809                   ui_state_samplerate,
2810                 &samplerate,
2811                 self->common.uris.
2812                   ui_state_sine_multiplier,
2813                 &sine_multiplier,
2814                 self->common.uris.
2815                   ui_state_saw_multiplier,
2816                 &saw_multiplier,
2817                 NULL);
2818               if (current_sample &&
2819                   current_sample->type ==
2820                     pl_common->uris.atom_Long &&
2821                   samplerate &&
2822                   samplerate->type ==
2823                     pl_common->uris.atom_Double &&
2824                   period_size &&
2825                   period_size->type ==
2826                     pl_common->uris.atom_Long &&
2827                   sine_multiplier &&
2828                   sine_multiplier->type ==
2829                     pl_common->uris.atom_Float &&
2830                   saw_multiplier &&
2831                   saw_multiplier->type ==
2832                     pl_common->uris.atom_Float)
2833                 {
2834                   self->common.current_sample =
2835                     ((LV2_Atom_Long*)
2836                      current_sample)->body;
2837                   self->current_sample =
2838                     (double)
2839                     self->common.current_sample;
2840                   SET_SAMPLERATE (
2841                     self,
2842                     ((LV2_Atom_Double*)
2843                      samplerate)->body);
2844                   self->common.period_size =
2845                     ((LV2_Atom_Long*)
2846                      period_size)->body;
2847                   self->common.sine_multiplier =
2848                     ((LV2_Atom_Float*)
2849                      sine_multiplier)->body;
2850                   self->common.saw_multiplier =
2851                     ((LV2_Atom_Float*)
2852                      saw_multiplier)->body;
2853                 }
2854               else
2855                 {
2856                   ztk_warning (
2857                     "failed to read UI state "
2858                     "atom");
2859                 }
2860             }
2861           if (obj->body.otype ==
2862                 pl_common->uris.time_Position)
2863             {
2864               update_position_from_atom_obj (
2865                 &self->common, obj);
2866             }
2867 
2868           self->has_change = 1;
2869 
2870 /*#if 0*/
2871           /*redraw_mid_region (self);*/
2872 /*#endif*/
2873         }
2874       else
2875         {
2876 #if 0
2877           log_error (
2878             self->log, &self->uris,
2879             "Unknown message type");
2880 #endif
2881         }
2882     }
2883   else
2884     {
2885 #if 0
2886       log_error (
2887         self->log, &self->uris,
2888         "Unknown format");
2889 #endif
2890     }
2891 }
2892 
2893 /* Optional non-embedded UI show interface. */
2894 static int
2895 ui_show (LV2UI_Handle handle)
2896 {
2897   printf ("show called\n");
2898   LfoUi * self = (LfoUi *) handle;
2899   ztk_app_show_window (self->app);
2900   return 0;
2901 }
2902 
2903 /* Optional non-embedded UI hide interface. */
2904 static int
2905 ui_hide (LV2UI_Handle handle)
2906 {
2907   printf ("hide called\n");
2908   LfoUi * self = (LfoUi *) handle;
2909   ztk_app_hide_window (self->app);
2910 
2911   return 0;
2912 }
2913 
2914 /**
2915  * LV2 idle interface for optional non-embedded
2916  * UI.
2917  */
2918 static int
2919 ui_idle (LV2UI_Handle handle)
2920 {
2921   LfoUi * self = (LfoUi *) handle;
2922 
2923   ztk_app_idle (self->app);
2924   redraw_mid_region (self);
2925 
2926   return 0;
2927 }
2928 
2929 /**
2930  * LV2 resize interface for the host.
2931  */
2932 static int
2933 ui_resize (
2934   LV2UI_Feature_Handle handle, int w, int h)
2935 {
2936   LfoUi * self = (LfoUi *) handle;
2937   self->resize->ui_resize (
2938     self->resize->handle, WIDTH, HEIGHT);
2939   return 0;
2940 }
2941 
2942 /**
2943  * Called by the host to get the idle and resize
2944  * functions.
2945  */
2946 static const void*
2947 extension_data (const char* uri)
2948 {
2949   static const LV2UI_Idle_Interface idle = {
2950     ui_idle };
2951   static const LV2UI_Resize resize = {
2952     0 ,ui_resize };
2953   static const LV2UI_Show_Interface show = {
2954     ui_show, ui_hide };
ExecParallelHashJoinSetUpBatches(HashJoinTable hashtable,int nbatch)2955   if (!strcmp(uri, LV2_UI__idleInterface))
2956     {
2957       return &idle;
2958     }
2959   if (!strcmp(uri, LV2_UI__resize))
2960     {
2961       return &resize;
2962     }
2963   if (!strcmp(uri, LV2_UI__showInterface))
2964     {
2965       return &show;
2966     }
2967   return NULL;
2968 }
2969 
2970 static const LV2UI_Descriptor descriptor = {
2971   PLUGIN_UI_URI,
2972   instantiate,
2973   cleanup,
2974   port_event,
2975   extension_data,
2976 };
2977 
2978 LV2_SYMBOL_EXPORT
2979 const LV2UI_Descriptor*
2980 lv2ui_descriptor (uint32_t index)
2981 {
2982   switch (index)
2983     {
2984     case 0:
2985       return &descriptor;
2986     default:
2987       return NULL;
2988     }
2989 }
2990