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 ¤t_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