1 /* robtk dyncomp gui
2 *
3 * Copyright 2019 Robin Gareus <robin@gareus.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include <assert.h>
21 #include <math.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25
26 #include "lv2/lv2plug.in/ns/ext/atom/atom.h"
27 #include "lv2/lv2plug.in/ns/ext/options/options.h"
28
29 #include "../src/darc.h"
30
31 #define RTK_USE_HOST_COLORS
32 #define RTK_URI DARC_URI
33 #define RTK_GUI "ui"
34
35 #ifndef MAX
36 #define MAX(A, B) ((A) > (B)) ? (A) : (B)
37 #endif
38
39 #ifndef MIN
40 #define MIN(A, B) ((A) < (B)) ? (A) : (B)
41 #endif
42
43 typedef struct {
44 LV2UI_Write_Function write;
45 LV2UI_Controller controller;
46 LV2UI_Touch* touch;
47
48 PangoFontDescription* font[2];
49
50 RobWidget* rw; // top-level container
51 RobWidget* ctbl; // control element table
52
53 /* Level + reduction drawing area */
54 RobWidget* m0;
55 int m0_width;
56 int m0_height;
57
58 /* Gain Mapping */
59 RobWidget* m1;
60
61 /* current gain */
62 float _gmin;
63 float _gmax;
64 float _rms;
65
66 /* control knobs */
67 RobTkDial* spn_ctrl[5];
68 RobTkLbl* lbl_ctrl[5];
69 RobTkCBtn* btn_hold;
70
71 cairo_surface_t* dial_bg[5];
72
73 /* gain meter */
74 cairo_pattern_t* m_fg;
75 cairo_pattern_t* m_bg;
76 cairo_surface_t* m0bg;
77
78 /* gain curve/mapping */
79 cairo_surface_t* m1_grid;
80 cairo_surface_t* m1_ctrl;
81 cairo_surface_t* m1_mask;
82
83 bool ctrl_dirty; // update m1_ctrl, m1_mask
84
85 /* tooltips */
86 int tt_id;
87 int tt_timeout;
88 cairo_rectangle_t* tt_pos;
89 cairo_rectangle_t* tt_box;
90
91 bool disable_signals;
92
93 RobWidget* m2;
94 const char* nfo;
95 } darcUI;
96
97 /* ****************************************************************************
98 * Control knob ranges and value mapping
99 */
100
101 struct CtrlRange {
102 float min;
103 float max;
104 float dflt;
105 float step;
106 float mult;
107 bool log;
108 const char* name;
109 };
110
111 const struct CtrlRange ctrl_range[] = {
112 /* clang-format off */
113 { -10, 30, 0, 0.2, 5, false, "Input Gain" },
114 { -50, -10, -30, 0.1, 5, false, "Threshold" },
115 { 0, 1, 0, 72, 2, true, "Ratio" },
116 { .001, .1, 0.01, 100, 5, true, "Attack" },
117 { .03, 3, 0.3, 100, 5, true, "Release" },
118 /* clang-format on */
119 };
120
121 static const char* tooltips[] = {
122 "<markup><b>Input Gain.</b> Gain applied before level detection\nor any other processing.\n(not visualized as x-axis offset in curve)</markup>",
123 "<markup><b>Threshold.</b> Signal level (RMS) at which\nthe compression effect is engaged.</markup>",
124 "<markup><b>Ratio.</b> The amount of gain or attenuation to be\napplied (dB/dB above threshold).\nUnity is retained at -10dBFS/RMS (auto makeup-gain).</markup>",
125 "<markup><b>Attack time.</b> Time it takes for the signal\nto become fully compressed after\nexceeding the threshold.</markup>",
126 "<markup><b>Release time.</b> Minimum recovery time\nto uncompressed signal-level\nafter falling below threshold.</markup>",
127 "<markup><b>Hold.</b> Retain current attenuation when the signal\nsubceeds the threshold.\nThis prevents modulation of the noise-floor\nand can counter-act 'pumping'.</markup>",
128 };
129
130 static float
ctrl_to_gui(const uint32_t c,const float v)131 ctrl_to_gui (const uint32_t c, const float v)
132 {
133 if (!ctrl_range[c].log) {
134 return v;
135 }
136 if (ctrl_range[c].min == 0) {
137 return v * v * ctrl_range[c].step;
138 }
139 const float r = logf (ctrl_range[c].max / ctrl_range[c].min);
140 return rintf (ctrl_range[c].step / r * (logf (v / ctrl_range[c].min)));
141 }
142
143 static float
gui_to_ctrl(const uint32_t c,const float v)144 gui_to_ctrl (const uint32_t c, const float v)
145 {
146 if (!ctrl_range[c].log) {
147 return v;
148 }
149 if (ctrl_range[c].min == 0) {
150 return sqrt (v / ctrl_range[c].step);
151 }
152 const float r = log (ctrl_range[c].max / ctrl_range[c].min);
153 return expf (logf (ctrl_range[c].min) + v * r / ctrl_range[c].step);
154 }
155
156 static float
k_min(const uint32_t c)157 k_min (const uint32_t c)
158 {
159 if (!ctrl_range[c].log) {
160 return ctrl_range[c].min;
161 }
162 return 0;
163 }
164
165 static float
k_max(const uint32_t c)166 k_max (const uint32_t c)
167 {
168 if (!ctrl_range[c].log) {
169 return ctrl_range[c].max;
170 }
171 return ctrl_range[c].step;
172 }
173
174 static float
k_step(const uint32_t c)175 k_step (const uint32_t c)
176 {
177 if (!ctrl_range[c].log) {
178 return ctrl_range[c].step;
179 }
180 return 1;
181 }
182
183 /* ****************************************************************************/
184
185 static float c_dlf[4] = { 0.8, 0.8, 0.8, 1.0 };
186
187 /* *****************************************************************************
188 * Knob faceplates
189 */
190
191 static void
prepare_faceplates(darcUI * ui)192 prepare_faceplates (darcUI* ui)
193 {
194 cairo_t* cr;
195 float xlp, ylp;
196
197 /* clang-format off */
198 #define INIT_DIAL_SF(VAR, W, H) \
199 VAR = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 2 * (W), 2 * (H)); \
200 cr = cairo_create (VAR); \
201 cairo_scale (cr, 2.0, 2.0); \
202 CairoSetSouerceRGBA (c_trs); \
203 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); \
204 cairo_rectangle (cr, 0, 0, W, H); \
205 cairo_fill (cr); \
206 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
207
208 #define DIALDOTS(V, XADD, YADD) \
209 float ang = (-.75 * M_PI) + (1.5 * M_PI) * (V); \
210 xlp = GED_CX + XADD + sinf (ang) * (GED_RADIUS + 3.0); \
211 ylp = GED_CY + YADD - cosf (ang) * (GED_RADIUS + 3.0); \
212 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); \
213 CairoSetSouerceRGBA (c_dlf); \
214 cairo_set_line_width (cr, 2.5); \
215 cairo_move_to (cr, rint (xlp) - .5, rint (ylp) - .5); \
216 cairo_close_path (cr); \
217 cairo_stroke (cr);
218
219 #define RESPLABLEL(V) \
220 { \
221 DIALDOTS (V, 4.5, 15.5) \
222 xlp = rint (GED_CX + 4.5 + sinf (ang) * (GED_RADIUS + 9.5)); \
223 ylp = rint (GED_CY + 15.5 - cosf (ang) * (GED_RADIUS + 9.5)); \
224 }
225 /* clang-format on */
226
227 INIT_DIAL_SF (ui->dial_bg[0], GED_WIDTH + 8, GED_HEIGHT + 20);
228 RESPLABLEL (0.00);
229 write_text_full (cr, "-10", ui->font[0], xlp + 6, ylp, 0, 1, c_dlf);
230 RESPLABLEL (0.25);
231 RESPLABLEL (0.5);
232 write_text_full (cr, "+10", ui->font[0], xlp - 2, ylp, 0, 2, c_dlf);
233 RESPLABLEL (.75);
234 RESPLABLEL (1.0);
235 write_text_full (cr, "+30", ui->font[0], xlp - 6, ylp, 0, 3, c_dlf);
236 cairo_destroy (cr);
237
238 INIT_DIAL_SF (ui->dial_bg[1], GED_WIDTH + 8, GED_HEIGHT + 20);
239 RESPLABLEL (0.00);
240 write_text_full (cr, "-50", ui->font[0], xlp + 6, ylp, 0, 1, c_dlf);
241 RESPLABLEL (0.25);
242 RESPLABLEL (0.5);
243 write_text_full (cr, "-30", ui->font[0], xlp - 2, ylp, 0, 2, c_dlf);
244 RESPLABLEL (.75);
245 RESPLABLEL (1.0);
246 write_text_full (cr, "-10", ui->font[0], xlp - 6, ylp, 0, 3, c_dlf);
247 cairo_destroy (cr);
248
249 INIT_DIAL_SF (ui->dial_bg[2], GED_WIDTH + 8, GED_HEIGHT + 20);
250 RESPLABLEL (0.00);
251 write_text_full (cr, "1", ui->font[0], xlp + 4, ylp, 0, 1, c_dlf);
252 RESPLABLEL (.25);
253 write_text_full (cr, "2", ui->font[0], xlp + 3, ylp, 0, 1, c_dlf);
254 RESPLABLEL (.44);
255 write_text_full (cr, "3", ui->font[0], xlp + 1, ylp, 0, 1, c_dlf);
256 RESPLABLEL (.64)
257 write_text_full (cr, "5", ui->font[0], xlp + 4, ylp, 0, 1, c_dlf);
258 RESPLABLEL (.81)
259 write_text_full (cr, "10", ui->font[0], xlp + 6, ylp, 0, 1, c_dlf);
260 RESPLABLEL (1.0);
261 write_text_full (cr, "Lim", ui->font[0], xlp - 9, ylp, 0, 3, c_dlf);
262 cairo_destroy (cr);
263
264 INIT_DIAL_SF (ui->dial_bg[3], GED_WIDTH + 8, GED_HEIGHT + 20);
265 RESPLABLEL (0.00);
266 write_text_full (cr, "1ms", ui->font[0], xlp + 9, ylp, 0, 1, c_dlf);
267 RESPLABLEL (.16);
268 RESPLABLEL (.33);
269 write_text_full (cr, "5", ui->font[0], xlp - 1, ylp, 0, 2, c_dlf);
270 RESPLABLEL (0.5);
271 RESPLABLEL (.66);
272 write_text_full (cr, "20", ui->font[0], xlp + 3, ylp, 0, 2, c_dlf);
273 RESPLABLEL (.83);
274 RESPLABLEL (1.0);
275 write_text_full (cr, "100", ui->font[0], xlp - 9, ylp, 0, 3, c_dlf);
276 cairo_destroy (cr);
277
278 INIT_DIAL_SF (ui->dial_bg[4], GED_WIDTH + 8, GED_HEIGHT + 20);
279 RESPLABLEL (0.00);
280 write_text_full (cr, "30ms", ui->font[0], xlp + 9, ylp, 0, 1, c_dlf);
281 RESPLABLEL (.16);
282 RESPLABLEL (.33);
283 write_text_full (cr, "150", ui->font[0], xlp - 5, ylp, 0, 2, c_dlf);
284 RESPLABLEL (0.5);
285 RESPLABLEL (.66);
286 write_text_full (cr, "600", ui->font[0], xlp + 5, ylp, 0, 2, c_dlf);
287 RESPLABLEL (.83);
288 RESPLABLEL (1.0);
289 write_text_full (cr, "3s", ui->font[0], xlp - 6, ylp, 0, 3, c_dlf);
290 cairo_destroy (cr);
291
292 #undef DIALDOTS
293 #undef INIT_DIAL_SF
294 #undef RESPLABLEL
295 }
296
297 /* *****************************************************************************
298 * Numeric value display - knob tooltips
299 */
300
301 static void
display_annotation(darcUI * ui,RobTkDial * d,cairo_t * cr,const char * txt)302 display_annotation (darcUI* ui, RobTkDial* d, cairo_t* cr, const char* txt)
303 {
304 int tw, th;
305 cairo_save (cr);
306 PangoLayout* pl = pango_cairo_create_layout (cr);
307 pango_layout_set_font_description (pl, ui->font[0]);
308 pango_layout_set_text (pl, txt, -1);
309 pango_layout_get_pixel_size (pl, &tw, &th);
310 cairo_translate (cr, d->w_width / 2, d->w_height - 2);
311 cairo_translate (cr, -tw / 2.0, -th);
312 cairo_set_source_rgba (cr, .0, .0, .0, .7);
313 rounded_rectangle (cr, -1, -1, tw + 3, th + 1, 3);
314 cairo_fill (cr);
315 CairoSetSouerceRGBA (c_wht);
316 pango_cairo_show_layout (cr, pl);
317 g_object_unref (pl);
318 cairo_restore (cr);
319 cairo_new_path (cr);
320 }
321
322 static void
dial_annotation_db(RobTkDial * d,cairo_t * cr,void * data)323 dial_annotation_db (RobTkDial* d, cairo_t* cr, void* data)
324 {
325 darcUI* ui = (darcUI*)(data);
326 char txt[16];
327 snprintf (txt, 16, "%5.1f dB", d->cur);
328 display_annotation (ui, d, cr, txt);
329 }
330
331 static void
format_msec(char * txt,const float val)332 format_msec (char* txt, const float val)
333 {
334 if (val < 0.03) {
335 snprintf (txt, 16, "%.1f ms", val * 1000.f);
336 } else if (val < 0.3) {
337 snprintf (txt, 16, "%.0f ms", val * 1000.f);
338 } else {
339 snprintf (txt, 16, "%.2f s", val);
340 }
341 }
342
343 static void
dial_annotation_tm(RobTkDial * d,cairo_t * cr,void * data)344 dial_annotation_tm (RobTkDial* d, cairo_t* cr, void* data)
345 {
346 darcUI* ui = (darcUI*)(data);
347 char txt[16];
348 assert (d == ui->spn_ctrl[3] || d == ui->spn_ctrl[4]);
349 const float val = gui_to_ctrl ((d == ui->spn_ctrl[3]) ? 3 : 4, d->cur);
350 format_msec (txt, val);
351 display_annotation (ui, d, cr, txt);
352 }
353
354 static void
dial_annotation_rr(RobTkDial * d,cairo_t * cr,void * data)355 dial_annotation_rr (RobTkDial* d, cairo_t* cr, void* data)
356 {
357 darcUI* ui = (darcUI*)(data);
358 char txt[16];
359 const float val = gui_to_ctrl (2, d->cur);
360 if (val >= 1) {
361 snprintf (txt, 16, "\u221E : 1");
362 } else if (val >= .9) {
363 snprintf (txt, 16, "%.0f : 1", 1 / (1.f - val));
364 } else {
365 snprintf (txt, 16, "%.1f : 1", 1 / (1.f - val));
366 }
367 display_annotation (ui, d, cr, txt);
368 }
369
370 /* *****************************************************************************
371 * knob & button callbacks
372 */
373
374 static bool
cb_spn_ctrl(RobWidget * w,void * handle)375 cb_spn_ctrl (RobWidget* w, void* handle)
376 {
377 darcUI* ui = (darcUI*)handle;
378 if (w == ui->spn_ctrl[1]->rw || w == ui->spn_ctrl[2]->rw) {
379 ui->ctrl_dirty = true;
380 queue_draw (ui->m1);
381 }
382
383 if (ui->disable_signals) {
384 return TRUE;
385 }
386
387 for (uint32_t i = 0; i < 5; ++i) {
388 if (w != ui->spn_ctrl[i]->rw) {
389 continue;
390 }
391 const float val = gui_to_ctrl (i, robtk_dial_get_value (ui->spn_ctrl[i]));
392 ui->write (ui->controller, DARC_INPUTGAIN + i, sizeof (float), 0, (const void*)&val);
393 break;
394 }
395 return TRUE;
396 }
397
398 static bool
cb_btn_hold(RobWidget * w,void * handle)399 cb_btn_hold (RobWidget* w, void* handle)
400 {
401 darcUI* ui = (darcUI*)handle;
402
403 ui->ctrl_dirty = true;
404 queue_draw (ui->m1);
405
406 if (ui->disable_signals) {
407 return TRUE;
408 }
409
410 const float val = robtk_cbtn_get_active (ui->btn_hold) ? 1.f : 0.f;
411 ui->write (ui->controller, DARC_HOLD, sizeof (float), 0, (const void*)&val);
412 return TRUE;
413 }
414
415 /* *****************************************************************************
416 * Tooltip & Help Overlay
417 */
418
419 static bool
tooltip_overlay(RobWidget * rw,cairo_t * cr,cairo_rectangle_t * ev)420 tooltip_overlay (RobWidget* rw, cairo_t* cr, cairo_rectangle_t* ev)
421 {
422 darcUI* ui = (darcUI*)rw->top;
423 assert (ui->tt_id >= 0 && ui->tt_id < 6);
424
425 cairo_save (cr);
426 cairo_rectangle_t event = { 0, 0, rw->area.width, rw->area.height };
427 rcontainer_clear_bg (rw, cr, &event);
428 rcontainer_expose_event (rw, cr, &event);
429 cairo_restore (cr);
430
431 const float top = ui->tt_box->y;
432 rounded_rectangle (cr, 0, top, rw->area.width, ui->tt_pos->y - top, 3);
433 cairo_set_source_rgba (cr, 0, 0, 0, .7);
434 cairo_fill (cr);
435
436 if (ui->tt_id < 5) {
437 rounded_rectangle (cr, ui->tt_pos->x, ui->tt_pos->y,
438 ui->tt_pos->width + 2, ui->tt_pos->height + 1, 3);
439 cairo_set_source_rgba (cr, 1, 1, 1, .5);
440 cairo_fill (cr);
441 }
442
443 const float* color = c_wht;
444 PangoFontDescription* font = pango_font_description_from_string ("Sans 11px");
445
446 const float xp = .5 * rw->area.width;
447 const float yp = .5 * (ui->tt_pos->y - top);
448
449 cairo_save (cr);
450 cairo_scale (cr, rw->widget_scale, rw->widget_scale);
451 write_text_full (cr, tooltips[ui->tt_id], font,
452 xp / rw->widget_scale, yp / rw->widget_scale,
453 0, 2, color);
454 cairo_restore (cr);
455
456 pango_font_description_free (font);
457
458 return TRUE;
459 }
460
461 static bool
tooltip_cnt(RobWidget * rw,cairo_t * cr,cairo_rectangle_t * ev)462 tooltip_cnt (RobWidget* rw, cairo_t* cr, cairo_rectangle_t* ev)
463 {
464 darcUI* ui = (darcUI*)rw->top;
465 if (++ui->tt_timeout < 12) {
466 rcontainer_expose_event (rw, cr, ev);
467 queue_draw (rw);
468 } else {
469 rw->expose_event = tooltip_overlay;
470 tooltip_overlay (rw, cr, ev);
471 }
472 return TRUE;
473 }
474
475 static void
ttip_handler(RobWidget * rw,bool on,void * handle)476 ttip_handler (RobWidget* rw, bool on, void* handle)
477 {
478 darcUI* ui = (darcUI*)handle;
479 ui->tt_id = -1;
480 ui->tt_timeout = 0;
481
482 for (int i = 0; i < 5; ++i) {
483 if (rw == ui->lbl_ctrl[i]->rw) {
484 ui->tt_id = i;
485 break;
486 }
487 }
488 if (rw == ui->btn_hold->rw) {
489 ui->tt_id = 5;
490 }
491
492 if (on && ui->tt_id >= 0) {
493 ui->tt_pos = &rw->area;
494 ui->tt_box = &ui->spn_ctrl[0]->rw->area;
495 ui->ctbl->expose_event = tooltip_cnt;
496 queue_draw (ui->ctbl);
497 } else {
498 ui->ctbl->expose_event = rcontainer_expose_event;
499 ui->ctbl->parent->resized = TRUE; // full re-expose
500 queue_draw (ui->rw);
501 }
502 }
503
504 static void
top_leave_notify(RobWidget * rw)505 top_leave_notify (RobWidget* rw)
506 {
507 darcUI* ui = (darcUI*)rw->children[1]->top;
508 if (ui->ctbl->expose_event != rcontainer_expose_event) {
509 ui->ctbl->expose_event = rcontainer_expose_event;
510 ui->ctbl->parent->resized = TRUE; //full re-expose
511 queue_draw (ui->rw);
512 }
513 }
514
515 /* *****************************************************************************
516 * Gain Meter Display
517 */
518
519 #define M0HEIGHT 36
520
521 static void
m0_size_request(RobWidget * handle,int * w,int * h)522 m0_size_request (RobWidget* handle, int* w, int* h)
523 {
524 darcUI* ui = (darcUI*)GET_HANDLE (handle);
525
526 *w = 300;
527 *h = M0HEIGHT * ui->rw->widget_scale;
528 }
529
530 static void
m0_size_allocate(RobWidget * handle,int w,int h)531 m0_size_allocate (RobWidget* handle, int w, int h)
532 {
533 darcUI* ui = (darcUI*)GET_HANDLE (handle);
534
535 h = M0HEIGHT * ui->rw->widget_scale;
536
537 ui->m0_width = w;
538 ui->m0_height = h;
539
540 robwidget_set_size (ui->m0, w, h);
541
542 if (ui->m_fg) {
543 cairo_pattern_destroy (ui->m_fg);
544 }
545 if (ui->m_bg) {
546 cairo_pattern_destroy (ui->m_bg);
547 }
548 if (ui->m0bg) {
549 cairo_surface_destroy (ui->m0bg);
550 }
551 ui->m_fg = NULL;
552 ui->m_bg = NULL;
553 ui->m0bg = NULL;
554
555 if (1) {
556 pango_font_description_free (ui->font[1]);
557 char fnt[32];
558 snprintf (fnt, 32, "Mono %.0fpx\n", 10 * sqrtf (h / (float)M0HEIGHT));
559 ui->font[1] = pango_font_description_from_string (fnt);
560 }
561
562 queue_draw (ui->m0);
563 }
564
565 static void
m0_render_faceplate(darcUI * ui,cairo_t * cr)566 m0_render_faceplate (darcUI* ui, cairo_t* cr)
567 {
568 const uint32_t yscale = ui->m0_height / M0HEIGHT;
569 const uint32_t top = (ui->m0_height - M0HEIGHT * yscale) * .5;
570 const uint32_t disp_w = ui->m0_width - 20; // deafult: 300
571
572 #define YPOS(y) (top + yscale * (y))
573 #define HGHT(y) (yscale * (y))
574
575 #define DEF(x) MAX (0, MIN (1., ((20. + (x)) / 60.)))
576 #define DEFLECT(x) (rint (disp_w * DEF (x)) - .5)
577
578 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
579 cairo_paint (cr);
580 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
581
582 CairoSetSouerceRGBA (c_blk);
583 rounded_rectangle (cr, 0, top, ui->m0_width, HGHT (M0HEIGHT), 6);
584 cairo_fill_preserve (cr);
585 cairo_clip (cr);
586
587 /* meter background */
588 cairo_set_source (cr, ui->m_bg);
589 cairo_rectangle (cr, 5, YPOS (4), disp_w + 10, HGHT (12));
590 cairo_fill (cr);
591
592 /* meter ticks */
593 cairo_set_line_width (cr, yscale);
594 CairoSetSouerceRGBA (c_wht);
595 for (int i = 0; i < 7; ++i) {
596 float dbx = DEFLECT (-20 + i * 10);
597 cairo_move_to (cr, 10 + dbx, YPOS (2));
598 cairo_line_to (cr, 10 + dbx, YPOS (18));
599 cairo_stroke (cr);
600
601 int tw, th;
602 PangoLayout* pl = pango_cairo_create_layout (cr);
603 pango_layout_set_font_description (pl, ui->font[1]);
604
605 if (i == 0) {
606 pango_layout_set_text (pl, "Gain:", -1);
607 pango_layout_get_pixel_size (pl, &tw, &th);
608 cairo_move_to (cr, 5 + dbx, YPOS (20));
609 pango_cairo_show_layout (cr, pl);
610 g_object_unref (pl);
611 continue;
612 }
613 char txt[16];
614 snprintf (txt, 16, "%+2d ", (i - 2) * 10);
615 pango_layout_set_text (pl, txt, -1);
616 pango_layout_get_pixel_size (pl, &tw, &th);
617 cairo_move_to (cr, 10 + dbx - tw * .5, YPOS (20));
618 pango_cairo_show_layout (cr, pl);
619 g_object_unref (pl);
620 }
621 }
622
623 static bool
m0_expose_event(RobWidget * handle,cairo_t * cr,cairo_rectangle_t * ev)624 m0_expose_event (RobWidget* handle, cairo_t* cr, cairo_rectangle_t* ev)
625 {
626 darcUI* ui = (darcUI*)GET_HANDLE (handle);
627 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
628 cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height);
629 cairo_clip_preserve (cr);
630
631 float c[4];
632 get_color_from_theme (1, c);
633 cairo_set_source_rgb (cr, c[0], c[1], c[2]);
634 cairo_fill (cr);
635
636 const uint32_t yscale = ui->m0_height / M0HEIGHT;
637 const uint32_t top = (ui->m0_height - M0HEIGHT * yscale) * .5;
638 const uint32_t disp_w = ui->m0_width - 20; // deafult: 300
639
640 #define YPOS(y) (top + yscale * (y))
641 #define HGHT(y) (yscale * (y))
642
643 #define DEF(x) MAX (0, MIN (1., ((20. + (x)) / 60.)))
644 #define DEFLECT(x) (rint (disp_w * DEF (x)) - .5)
645
646 if (!ui->m_fg) {
647 cairo_pattern_t* pat = cairo_pattern_create_linear (10, 0.0, disp_w, 0.0);
648 /* clang-format off */
649 cairo_pattern_add_color_stop_rgb (pat, DEF (40), .1, .9, .1);
650 cairo_pattern_add_color_stop_rgb (pat, DEF (5), .1, .9, .1);
651 cairo_pattern_add_color_stop_rgb (pat, DEF (-5), .9, .9, .1);
652 cairo_pattern_add_color_stop_rgb (pat, DEF (-20), .9, .9, .1);
653 /* clang-format on */
654 ui->m_fg = pat;
655 }
656
657 if (!ui->m_bg) {
658 const float alpha = 0.5;
659 cairo_pattern_t* pat = cairo_pattern_create_linear (10, 0.0, disp_w, 0.0);
660 /* clang-format off */
661 cairo_pattern_add_color_stop_rgba (pat, DEF (40), .0, .5, .0, alpha);
662 cairo_pattern_add_color_stop_rgba (pat, DEF (5), .0, .5, .0, alpha);
663 cairo_pattern_add_color_stop_rgba (pat, DEF (-5), .5, .0, .0, alpha);
664 cairo_pattern_add_color_stop_rgba (pat, DEF (-20), .5, .0, .0, alpha);
665 /* clang-format on */
666 ui->m_bg = pat;
667 }
668
669 if (!ui->m0bg) {
670 ui->m0bg = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, ui->m0_width, ui->m0_height);
671 cairo_t* icr = cairo_create (ui->m0bg);
672 m0_render_faceplate (ui, icr);
673 cairo_destroy (icr);
674 }
675
676 cairo_set_source_surface (cr, ui->m0bg, 0, 0);
677 cairo_paint (cr);
678
679 /* current reduction */
680 float v0 = DEFLECT (ui->_gmin);
681 float v1 = DEFLECT (ui->_gmax);
682 cairo_rectangle (cr, 7.5 + v0, YPOS (4), 5 + v1 - v0, HGHT (12));
683 cairo_set_source (cr, ui->m_fg);
684 cairo_fill (cr);
685
686 return TRUE;
687 }
688
689 /* ****************************************************************************/
690
691 static float
comp_curve(float in,float threshold,float ratio,bool hold)692 comp_curve (float in, float threshold, float ratio, bool hold)
693 {
694 float key = in;
695 if (hold && in < threshold) {
696 key = threshold;
697 }
698 #ifdef __USE_GNU
699 float g = logf (exp10f (1.f + .1f * threshold) + exp10f (1.f + .1f * key));
700 #else
701 float g = logf (powf (10.f, 1.f + .1f * threshold) + powf (10.f, 1.f + .1f * key));
702 #endif
703 return /*-10/log(10)*/ -4.342944819f * ratio * g + in;
704 }
705
706 /* ****************************************************************************/
707
708 #define M1RECT 350
709
710 static void
m1_size_request(RobWidget * handle,int * w,int * h)711 m1_size_request (RobWidget* handle, int* w, int* h)
712 {
713 darcUI* ui = (darcUI*)GET_HANDLE (handle);
714
715 *w = M1RECT * ui->rw->widget_scale;
716 *h = M1RECT * ui->rw->widget_scale;
717 }
718
719 static void
m1_size_allocate(RobWidget * handle,int w,int h)720 m1_size_allocate (RobWidget* handle, int w, int h)
721 {
722 darcUI* ui = (darcUI*)GET_HANDLE (handle);
723
724 if (ui->m1_grid) {
725 cairo_surface_destroy (ui->m1_grid);
726 }
727 if (ui->m1_ctrl) {
728 cairo_surface_destroy (ui->m1_ctrl);
729 }
730 if (ui->m1_mask) {
731 cairo_surface_destroy (ui->m1_mask);
732 }
733 ui->m1_grid = NULL;
734 ui->m1_ctrl = NULL;
735 ui->m1_mask = NULL;
736
737 queue_draw (ui->m1);
738 }
739
740 static void
m1_render_grid(darcUI * ui,cairo_t * cr)741 m1_render_grid (darcUI* ui, cairo_t* cr)
742 {
743 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
744 cairo_paint (cr);
745 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
746
747 cairo_scale (cr, ui->rw->widget_scale, ui->rw->widget_scale);
748
749 CairoSetSouerceRGBA (c_blk);
750 rounded_rectangle (cr, 0, 0, M1RECT, M1RECT, 8);
751 cairo_fill_preserve (cr);
752 cairo_clip (cr);
753
754 /* draw grid 10dB steps */
755 cairo_set_line_width (cr, 1.0);
756
757 const double dash1[] = { 1, 2 };
758 const double dash2[] = { 1, 3 };
759
760 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
761 cairo_set_dash (cr, dash2, 2, 2);
762
763 for (uint32_t d = 1; d < 7; ++d) {
764 const float x = -.5 + floor (M1RECT * d * 10.f / 70.f);
765 const float y = -.5 + floor (M1RECT * (70 - d * 10.f) / 70.f);
766
767 cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.5);
768
769 cairo_move_to (cr, x, 0);
770 cairo_line_to (cr, x, M1RECT);
771 cairo_stroke (cr);
772
773 cairo_move_to (cr, 0, y);
774 cairo_line_to (cr, M1RECT, y);
775 cairo_stroke (cr);
776
777 char txt[16];
778 snprintf (txt, 16, "%+2d", -60 + d * 10);
779 write_text_full (cr, txt, ui->font[1], x, M1RECT * (10.f / 70.f) - 2, 0, 5, c_dlf);
780 if (d != 6) {
781 write_text_full (cr, txt, ui->font[1], M1RECT * (60.f / 70.f) + 2, y, M_PI * .5, 5, c_dlf);
782 }
783 }
784
785 /* diagonal unity */
786 cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 1.0);
787 cairo_set_dash (cr, dash1, 2, 2);
788 cairo_move_to (cr, 0, M1RECT);
789 cairo_line_to (cr, M1RECT, 0);
790 cairo_stroke (cr);
791
792 cairo_set_dash (cr, 0, 0, 0);
793
794 write_text_full (cr, "Output", ui->font[0], M1RECT * (65.f / 70.f), M1RECT * .5, M_PI * .5, 5, c_dlf);
795 write_text_full (cr, "Input [dBFS/RMS]", ui->font[0], M1RECT * .5, M1RECT * (5.f / 70.f), 0, 5, c_dlf);
796
797 /* 0dBFS limit indicator */
798 cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.5);
799 const float x = -.5 + floor (M1RECT * 60.f / 70.f);
800 const float y = -.5 + floor (M1RECT * 10.f / 70.f);
801 cairo_move_to (cr, x, 0);
802 cairo_line_to (cr, x, M1RECT);
803 cairo_stroke (cr);
804 cairo_move_to (cr, 0, y);
805 cairo_line_to (cr, M1RECT, y);
806 cairo_stroke (cr);
807 }
808
809 static void
m1_render_mask(darcUI * ui)810 m1_render_mask (darcUI* ui)
811 {
812 if (ui->m1_ctrl) {
813 cairo_surface_destroy (ui->m1_ctrl);
814 }
815 if (ui->m1_mask) {
816 cairo_surface_destroy (ui->m1_mask);
817 }
818
819 int sq = M1RECT * ui->rw->widget_scale;
820 ui->m1_ctrl = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, sq, sq);
821 ui->m1_mask = cairo_image_surface_create (CAIRO_FORMAT_A8, M1RECT, M1RECT);
822
823 cairo_t* cr = cairo_create (ui->m1_ctrl);
824 cairo_t* cm = cairo_create (ui->m1_mask);
825
826 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
827 cairo_paint (cr);
828 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
829 cairo_set_operator (cm, CAIRO_OPERATOR_CLEAR);
830 cairo_paint (cm);
831 cairo_set_operator (cm, CAIRO_OPERATOR_OVER);
832
833 cairo_scale (cr, ui->rw->widget_scale, ui->rw->widget_scale);
834
835 rounded_rectangle (cr, 0, 0, M1RECT, M1RECT, 8);
836 cairo_clip (cr);
837
838 rounded_rectangle (cm, 0, 0, M1RECT, M1RECT, 8);
839 cairo_clip (cm);
840
841 const float thrsh = gui_to_ctrl (1, robtk_dial_get_value (ui->spn_ctrl[1]));
842 const float ratio = gui_to_ctrl (2, robtk_dial_get_value (ui->spn_ctrl[2]));
843 const bool hold = robtk_cbtn_get_active (ui->btn_hold);
844
845 cairo_set_source_rgba (cr, .8, .8, .8, 1.0);
846 cairo_set_line_width (cr, 1.0);
847
848 if (hold) {
849 cairo_move_to (cr, 0, M1RECT * ((comp_curve (-60, thrsh, ratio, true) - 10) / -70.f));
850
851 uint32_t x = 1;
852 for (; x <= M1RECT; ++x) {
853 const float x_db = 70.f * (-1.f + x / (float)M1RECT) + 10.f;
854 const float y_db = comp_curve (x_db, thrsh, ratio, true) - 10;
855 const float y = M1RECT * (y_db / -70.f);
856 cairo_line_to (cr, x, y);
857 if (x_db > thrsh) {
858 break;
859 }
860 }
861
862 const double dash1[] = { 1, 2, 4, 2 };
863 cairo_set_dash (cr, dash1, 4, 0);
864 cairo_stroke_preserve (cr);
865 cairo_set_dash (cr, NULL, 0, 0);
866
867 for (; x > 0; --x) {
868 const float x_db = 70.f * (-1.f + x / (float)M1RECT) + 10.f;
869 const float y_db = comp_curve (x_db, thrsh, ratio, false) - 10;
870 const float y = M1RECT * (y_db / -70.f);
871 cairo_line_to (cr, x, y);
872 }
873
874 cairo_close_path (cr);
875
876 cairo_set_source_rgba (cr, 0, 0, .5, .5);
877 cairo_fill (cr);
878 }
879
880 cairo_move_to (cr, 0, M1RECT * ((comp_curve (-60, thrsh, ratio, false) - 10) / -70.f));
881 cairo_move_to (cm, 0, M1RECT * ((comp_curve (-60, thrsh, ratio, hold) - 10) / -70.f));
882
883 cairo_set_source_rgba (cr, .8, .8, .8, 1.0);
884
885 for (uint32_t x = 1; x <= M1RECT; ++x) {
886 const float x_db = 70.f * (-1.f + x / (float)M1RECT) + 10.f;
887 const float y_db = comp_curve (x_db, thrsh, ratio, false) - 10;
888 const float h_db = comp_curve (x_db, thrsh, ratio, hold) - 10;
889 cairo_line_to (cr, x, M1RECT * (y_db / -70.f));
890 cairo_line_to (cm, x, M1RECT * (h_db / -70.f));
891 }
892 cairo_stroke_preserve (cr);
893
894 cairo_line_to (cr, M1RECT, M1RECT);
895 cairo_line_to (cr, 0, M1RECT);
896 cairo_close_path (cr);
897
898 cairo_line_to (cm, M1RECT, M1RECT);
899 cairo_line_to (cm, 0, M1RECT);
900 cairo_close_path (cm);
901
902 cairo_set_source_rgba (cr, 1, 1, 1, .1);
903 cairo_fill (cr);
904
905 cairo_set_source_rgba (cm, 1, 1, 1, 1);
906 cairo_fill (cm);
907
908 cairo_destroy (cr);
909 cairo_destroy (cm);
910 }
911
912 static bool
m1_expose_event(RobWidget * handle,cairo_t * cr,cairo_rectangle_t * ev)913 m1_expose_event (RobWidget* handle, cairo_t* cr, cairo_rectangle_t* ev)
914 {
915 darcUI* ui = (darcUI*)GET_HANDLE (handle);
916 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
917 cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height);
918 cairo_clip_preserve (cr);
919
920 float c[4];
921 get_color_from_theme (1, c);
922 cairo_set_source_rgb (cr, c[0], c[1], c[2]);
923 cairo_fill (cr);
924
925 if (!ui->m1_grid) {
926 int sq = M1RECT * ui->rw->widget_scale;
927 ui->m1_grid = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, sq, sq);
928 cairo_t* icr = cairo_create (ui->m1_grid);
929 m1_render_grid (ui, icr);
930 cairo_destroy (icr);
931 }
932
933 if (!ui->m1_ctrl || !ui->m1_mask || ui->ctrl_dirty) {
934 ui->ctrl_dirty = false;
935 m1_render_mask (ui);
936 }
937
938 cairo_set_source_surface (cr, ui->m1_grid, 0, 0);
939 cairo_paint (cr);
940
941 cairo_set_source_surface (cr, ui->m1_ctrl, 0, 0);
942 cairo_paint (cr);
943
944 cairo_scale (cr, ui->rw->widget_scale, ui->rw->widget_scale);
945
946 const float thrsh = gui_to_ctrl (1, robtk_dial_get_value (ui->spn_ctrl[1]));
947 const bool hold = robtk_cbtn_get_active (ui->btn_hold);
948
949 float thx = (thrsh + 60.f) * M1RECT / 70.f;
950 if (hold) {
951 /* shade area where hold is active */
952 cairo_save (cr);
953 cairo_rectangle (cr, 0, 0, thx, M1RECT);
954 cairo_clip (cr);
955 cairo_set_source_rgba (cr, 0.0, 0.0, .7, .1);
956 cairo_mask_surface (cr, ui->m1_mask, 0, 0);
957 cairo_fill (cr);
958 cairo_restore (cr);
959 }
960
961 cairo_set_line_width (cr, 1);
962 cairo_move_to (cr, floor (thx) - .5, M1RECT * 9.f / 70.f);
963 cairo_line_to (cr, floor (thx) - .5, M1RECT);
964 cairo_set_source_rgba (cr, .8, .7, .1, .9);
965 const double dash1[] = { 1, 1 };
966 cairo_set_dash (cr, dash1, 2, 0);
967 cairo_stroke (cr);
968 cairo_set_dash (cr, NULL, 0, 0);
969
970 float pkx = (ui->_rms + 60.f) * M1RECT / 70.f;
971 if (pkx > 0) {
972 cairo_save (cr);
973 cairo_rectangle (cr, 0, 0, MIN (M1RECT, pkx), M1RECT);
974 cairo_clip (cr);
975 cairo_set_source_rgba (cr, 0.6, 0.6, 0.6, 0.5);
976 cairo_mask_surface (cr, ui->m1_mask, 0, 0);
977 cairo_fill (cr);
978 cairo_restore (cr);
979
980 cairo_save (cr);
981 cairo_rectangle (cr, 0, 0, MIN (M1RECT, pkx + 6), M1RECT);
982 cairo_clip (cr);
983 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
984
985 float pky0 = (ui->_rms + ui->_gmax - 10.f) * M1RECT / -70.f;
986 float pky1 = (ui->_rms + ui->_gmin - 10.f) * M1RECT / -70.f;
987 cairo_move_to (cr, pkx, pky0);
988 cairo_line_to (cr, pkx, pky1);
989 cairo_set_line_width (cr, 5);
990 cairo_set_source_rgba (cr, 1, 1, 1, 1);
991 cairo_stroke (cr);
992 cairo_restore (cr);
993 }
994
995 return TRUE;
996 }
997
998 /* ****************************************************************************/
999
1000 static void
m2_size_request(RobWidget * handle,int * w,int * h)1001 m2_size_request (RobWidget* handle, int* w, int* h)
1002 {
1003 *w = 12;
1004 *h = 10;
1005 }
1006
1007 static void
m2_size_allocate(RobWidget * rw,int w,int h)1008 m2_size_allocate (RobWidget* rw, int w, int h)
1009 {
1010 robwidget_set_size (rw, w, h);
1011 }
1012
1013 static bool
m2_expose_event(RobWidget * rw,cairo_t * cr,cairo_rectangle_t * ev)1014 m2_expose_event (RobWidget* rw, cairo_t* cr, cairo_rectangle_t* ev)
1015 {
1016 darcUI* ui = (darcUI*)GET_HANDLE (rw);
1017 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1018 cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height);
1019 cairo_clip (cr);
1020 cairo_rectangle (cr, 0, 0, rw->area.width, rw->area.height);
1021 cairo_clip_preserve (cr);
1022
1023 float c[4];
1024 get_color_from_theme (1, c);
1025 cairo_set_source_rgb (cr, c[0], c[1], c[2]);
1026 cairo_fill (cr);
1027
1028 cairo_scale (cr, ui->rw->widget_scale, ui->rw->widget_scale);
1029 if (ui->nfo) {
1030 write_text_full (cr, ui->nfo, ui->font[0], 0, .5 * rw->area.height / ui->rw->widget_scale, 0, 3, c_gry);
1031 }
1032 return TRUE;
1033 }
1034
1035 /* ****************************************************************************/
1036
1037 static RobWidget*
toplevel(darcUI * ui,void * const top)1038 toplevel (darcUI* ui, void* const top)
1039 {
1040 /* main widget: layout */
1041 ui->rw = rob_vbox_new (FALSE, 2);
1042 robwidget_make_toplevel (ui->rw, top);
1043 robwidget_toplevel_enable_scaling (ui->rw);
1044
1045 ui->font[0] = pango_font_description_from_string ("Mono 9px");
1046 ui->font[1] = pango_font_description_from_string ("Mono 10px");
1047
1048 prepare_faceplates (ui);
1049
1050 /* level display */
1051 ui->m0 = robwidget_new (ui);
1052 robwidget_set_alignment (ui->m0, .5, .5);
1053 robwidget_set_expose_event (ui->m0, m0_expose_event);
1054 robwidget_set_size_request (ui->m0, m0_size_request);
1055 robwidget_set_size_allocate (ui->m0, m0_size_allocate);
1056
1057 /* graph display */
1058 ui->m1 = robwidget_new (ui);
1059 robwidget_set_alignment (ui->m1, .5, .5);
1060 robwidget_set_expose_event (ui->m1, m1_expose_event);
1061 robwidget_set_size_request (ui->m1, m1_size_request);
1062 robwidget_set_size_allocate (ui->m1, m1_size_allocate);
1063
1064 /* control knob table */
1065 ui->ctbl = rob_table_new (/*rows*/ 3, /*cols*/ 5, FALSE);
1066 ui->ctbl->top = (void*)ui;
1067
1068 #define GSP_W(PTR) robtk_dial_widget (PTR)
1069 #define GLB_W(PTR) robtk_lbl_widget (PTR)
1070 #define GBT_W(PTR) robtk_cbtn_widget (PTR)
1071
1072 for (uint32_t i = 0; i < 5; ++i) {
1073 ui->lbl_ctrl[i] = robtk_lbl_new (ctrl_range[i].name);
1074 ui->spn_ctrl[i] = robtk_dial_new_with_size (
1075 k_min (i), k_max (i), k_step (i),
1076 GED_WIDTH + 8, GED_HEIGHT + 20, GED_CX + 4, GED_CY + 15, GED_RADIUS);
1077 ui->spn_ctrl[i]->with_scroll_accel = false;
1078
1079 robtk_dial_set_value (ui->spn_ctrl[i], ctrl_to_gui (i, ctrl_range[i].dflt));
1080 robtk_dial_set_callback (ui->spn_ctrl[i], cb_spn_ctrl, ui);
1081 robtk_dial_set_default (ui->spn_ctrl[i], ctrl_to_gui (i, ctrl_range[i].dflt));
1082 robtk_dial_set_scroll_mult (ui->spn_ctrl[i], ctrl_range[i].mult);
1083
1084 if (ui->touch) {
1085 robtk_dial_set_touch (ui->spn_ctrl[i], ui->touch->touch, ui->touch->handle, DARC_INPUTGAIN + i);
1086 }
1087
1088 robtk_dial_set_scaled_surface_scale (ui->spn_ctrl[i], ui->dial_bg[i], 2.0);
1089 robtk_lbl_annotation_callback (ui->lbl_ctrl[i], ttip_handler, ui);
1090
1091 rob_table_attach (ui->ctbl, GSP_W (ui->spn_ctrl[i]), i, i + 1, 0, 1, 4, 0, RTK_EXANDF, RTK_SHRINK);
1092 rob_table_attach (ui->ctbl, GLB_W (ui->lbl_ctrl[i]), i, i + 1, 1, 2, 4, 0, RTK_EXANDF, RTK_SHRINK);
1093 }
1094
1095 /* snap at 0dB gain */
1096 robtk_dial_set_detent_default (ui->spn_ctrl[0], true);
1097
1098 /* use 'dot' for time knobs */
1099 ui->spn_ctrl[3]->displaymode = 3;
1100 ui->spn_ctrl[4]->displaymode = 3; // use dot
1101
1102 /* these numerics are meaningful */
1103 robtk_dial_annotation_callback (ui->spn_ctrl[0], dial_annotation_db, ui);
1104 robtk_dial_annotation_callback (ui->spn_ctrl[1], dial_annotation_db, ui);
1105 robtk_dial_annotation_callback (ui->spn_ctrl[2], dial_annotation_rr, ui);
1106 robtk_dial_annotation_callback (ui->spn_ctrl[3], dial_annotation_tm, ui);
1107 robtk_dial_annotation_callback (ui->spn_ctrl[4], dial_annotation_tm, ui);
1108
1109 /* custom knob colors */
1110 {
1111 const float c_bg[4] = { .7, .7, .1, 1.0 };
1112 create_dial_pattern (ui->spn_ctrl[0], c_bg);
1113 ui->spn_ctrl[0]->dcol[0][0] =
1114 ui->spn_ctrl[0]->dcol[0][1] =
1115 ui->spn_ctrl[0]->dcol[0][2] = .05;
1116 }
1117 {
1118 const float c_bg[4] = { .8, .3, .0, 1.0 };
1119 create_dial_pattern (ui->spn_ctrl[1], c_bg);
1120 ui->spn_ctrl[1]->dcol[0][0] =
1121 ui->spn_ctrl[1]->dcol[0][1] =
1122 ui->spn_ctrl[1]->dcol[0][2] = .05;
1123 }
1124 {
1125 const float c_bg[4] = { .9, .2, .2, 1.0 };
1126 create_dial_pattern (ui->spn_ctrl[2], c_bg);
1127 ui->spn_ctrl[2]->dcol[0][0] =
1128 ui->spn_ctrl[2]->dcol[0][1] =
1129 ui->spn_ctrl[2]->dcol[0][2] = .05;
1130 }
1131 {
1132 const float c_bg[4] = { .3, .3, .7, 1.0 };
1133 create_dial_pattern (ui->spn_ctrl[3], c_bg);
1134 ui->spn_ctrl[3]->dcol[0][0] =
1135 ui->spn_ctrl[3]->dcol[0][1] =
1136 ui->spn_ctrl[3]->dcol[0][2] = .05;
1137 create_dial_pattern (ui->spn_ctrl[4], c_bg);
1138 ui->spn_ctrl[4]->dcol[0][0] =
1139 ui->spn_ctrl[4]->dcol[0][1] =
1140 ui->spn_ctrl[4]->dcol[0][2] = .05;
1141 }
1142
1143 /* explicit hold button */
1144 ui->btn_hold = robtk_cbtn_new ("Hold", GBT_LED_RIGHT, false);
1145 robtk_cbtn_set_callback (ui->btn_hold, cb_btn_hold, ui);
1146 rob_table_attach (ui->ctbl, GBT_W (ui->btn_hold), 4, 5, 3, 4, 8, 2, RTK_EXANDF, RTK_SHRINK);
1147
1148 robtk_cbtn_set_temporary_mode (ui->btn_hold, 1);
1149 robtk_cbtn_set_color_on (ui->btn_hold, 0.1, 0.3, 0.8);
1150 robtk_cbtn_set_color_off (ui->btn_hold, .1, .1, .3);
1151 robtk_cbtn_annotation_callback (ui->btn_hold, ttip_handler, ui);
1152
1153 /* version info */
1154
1155 ui->m2 = robwidget_new (ui);
1156 robwidget_set_alignment (ui->m2, 0, 0);
1157 robwidget_set_expose_event (ui->m2, m2_expose_event);
1158 robwidget_set_size_request (ui->m2, m2_size_request);
1159 robwidget_set_size_allocate (ui->m2, m2_size_allocate);
1160
1161 rob_table_attach (ui->ctbl, ui->m2, 0, 2, 3, 4, 8, 2, RTK_FILL, RTK_FILL);
1162
1163 /* top-level packing */
1164 rob_vbox_child_pack (ui->rw, ui->m1, FALSE, TRUE);
1165 rob_vbox_child_pack (ui->rw, ui->ctbl, FALSE, TRUE);
1166 rob_vbox_child_pack (ui->rw, ui->m0, TRUE, TRUE);
1167 robwidget_set_leave_notify(ui->rw, top_leave_notify);
1168 return ui->rw;
1169 }
1170
1171 static void
gui_cleanup(darcUI * ui)1172 gui_cleanup (darcUI* ui)
1173 {
1174 for (int i = 0; i < 5; ++i) {
1175 robtk_dial_destroy (ui->spn_ctrl[i]);
1176 robtk_lbl_destroy (ui->lbl_ctrl[i]);
1177 cairo_surface_destroy (ui->dial_bg[i]);
1178 }
1179
1180 pango_font_description_free (ui->font[0]);
1181 pango_font_description_free (ui->font[1]);
1182
1183 if (ui->m_fg) {
1184 cairo_pattern_destroy (ui->m_fg);
1185 }
1186 if (ui->m_bg) {
1187 cairo_pattern_destroy (ui->m_bg);
1188 }
1189 if (ui->m0bg) {
1190 cairo_surface_destroy (ui->m0bg);
1191 }
1192 if (ui->m1_grid) {
1193 cairo_surface_destroy (ui->m1_grid);
1194 }
1195 if (ui->m1_ctrl) {
1196 cairo_surface_destroy (ui->m1_ctrl);
1197 }
1198 if (ui->m1_mask) {
1199 cairo_surface_destroy (ui->m1_mask);
1200 }
1201
1202 robtk_cbtn_destroy (ui->btn_hold);
1203 robwidget_destroy (ui->m0);
1204 robwidget_destroy (ui->m1);
1205 robwidget_destroy (ui->m2);
1206 rob_table_destroy (ui->ctbl);
1207 rob_box_destroy (ui->rw);
1208 }
1209
1210 /* *****************************************************************************
1211 * RobTk + LV2
1212 */
1213
1214 #define LVGL_RESIZEABLE
1215
1216 static void
ui_enable(LV2UI_Handle handle)1217 ui_enable (LV2UI_Handle handle)
1218 {
1219 }
1220
1221 static void
ui_disable(LV2UI_Handle handle)1222 ui_disable (LV2UI_Handle handle)
1223 {
1224 }
1225
1226 static enum LVGLResize
plugin_scale_mode(LV2UI_Handle handle)1227 plugin_scale_mode (LV2UI_Handle handle)
1228 {
1229 return LVGL_LAYOUT_TO_FIT;
1230 }
1231
1232 static LV2UI_Handle
instantiate(void * const ui_toplevel,const LV2UI_Descriptor * descriptor,const char * plugin_uri,const char * bundle_path,LV2UI_Write_Function write_function,LV2UI_Controller controller,RobWidget ** widget,const LV2_Feature * const * features)1233 instantiate (
1234 void* const ui_toplevel,
1235 const LV2UI_Descriptor* descriptor,
1236 const char* plugin_uri,
1237 const char* bundle_path,
1238 LV2UI_Write_Function write_function,
1239 LV2UI_Controller controller,
1240 RobWidget** widget,
1241 const LV2_Feature* const* features)
1242 {
1243 darcUI* ui = (darcUI*)calloc (1, sizeof (darcUI));
1244 if (!ui) {
1245 return NULL;
1246 }
1247
1248 if (strcmp (plugin_uri, RTK_URI "mono") && strcmp (plugin_uri, RTK_URI "stereo")) {
1249 free (ui);
1250 return NULL;
1251 }
1252
1253 const LV2_Options_Option* options = NULL;
1254 const LV2_URID_Map* map = NULL;
1255
1256 for (int i = 0; features[i]; ++i) {
1257 if (!strcmp (features[i]->URI, LV2_UI__touch)) {
1258 ui->touch = (LV2UI_Touch*)features[i]->data;
1259 } else if (!strcmp (features[i]->URI, LV2_URID__map)) {
1260 map = (LV2_URID_Map*)features[i]->data;
1261 } else if (!strcmp (features[i]->URI, LV2_OPTIONS__options)) {
1262 options = (LV2_Options_Option*)features[i]->data;
1263 }
1264 }
1265
1266 interpolate_fg_bg (c_dlf, .2);
1267
1268 ui->nfo = robtk_info (ui_toplevel);
1269 ui->write = write_function;
1270 ui->controller = controller;
1271 ui->ctrl_dirty = false;
1272 ui->disable_signals = true;
1273 *widget = toplevel (ui, ui_toplevel);
1274 ui->disable_signals = false;
1275
1276 if (options && map) {
1277 LV2_URID atom_Float = map->map (map->handle, LV2_ATOM__Float);
1278 LV2_URID ui_scale = map->map (map->handle, "http://lv2plug.in/ns/extensions/ui#scaleFactor");
1279 for (const LV2_Options_Option* o = options; o->key; ++o) {
1280 if (o->context == LV2_OPTIONS_INSTANCE && o->key == ui_scale && o->type == atom_Float) {
1281 float ui_scale = *(const float*)o->value;
1282 if (ui_scale < 1.0) {
1283 ui_scale = 1.0;
1284 }
1285 if (ui_scale > 2.0) {
1286 ui_scale = 2.0;
1287 }
1288 robtk_queue_scale_change (ui->rw, ui_scale);
1289 }
1290 }
1291 }
1292 return ui;
1293 }
1294
1295 static void
cleanup(LV2UI_Handle handle)1296 cleanup (LV2UI_Handle handle)
1297 {
1298 darcUI* ui = (darcUI*)handle;
1299 gui_cleanup (ui);
1300 free (ui);
1301 }
1302
1303 /* receive information from DSP */
1304 static void
port_event(LV2UI_Handle handle,uint32_t port_index,uint32_t buffer_size,uint32_t format,const void * buffer)1305 port_event (LV2UI_Handle handle,
1306 uint32_t port_index,
1307 uint32_t buffer_size,
1308 uint32_t format,
1309 const void* buffer)
1310 {
1311 darcUI* ui = (darcUI*)handle;
1312
1313 if (format != 0) {
1314 return;
1315 }
1316
1317 if (port_index == DARC_GMIN) {
1318 ui->_gmin = *(float*)buffer;
1319 /* TODO: partial exposure, only redraw changed gain area */
1320 queue_draw (ui->m0);
1321 queue_draw (ui->m1);
1322 } else if (port_index == DARC_GMAX) {
1323 ui->_gmax = *(float*)buffer;
1324 queue_draw (ui->m0);
1325 queue_draw (ui->m1);
1326 } else if (port_index == DARC_RMS) {
1327 ui->_rms = *(float*)buffer;
1328 queue_draw (ui->m1);
1329 } else if (port_index == DARC_HOLD) {
1330 ui->disable_signals = true;
1331 robtk_cbtn_set_active (ui->btn_hold, (*(float*)buffer) > 0);
1332 ui->disable_signals = false;
1333 } else if (port_index >= DARC_INPUTGAIN && port_index <= DARC_RELEASE) {
1334 const float v = *(float*)buffer;
1335 ui->disable_signals = true;
1336 uint32_t ctrl = port_index - DARC_INPUTGAIN;
1337 robtk_dial_set_value (ui->spn_ctrl[ctrl], ctrl_to_gui (ctrl, v));
1338 ui->disable_signals = false;
1339 }
1340 }
1341
1342 static const void*
extension_data(const char * uri)1343 extension_data (const char* uri)
1344 {
1345 return NULL;
1346 }
1347