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