1 /* meters.lv2
2  *
3  * Copyright (C) 2013 Robin Gareus <robin@gareus.org>
4  * Copyright (C) 2008-2012 Fons Adriaensen <fons@linuxaudio.org>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2, or (at your option)
9  * any later version.
10  *
11  * This program 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 General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  */
20 
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <math.h>
25 
26 #include "img/screw.c"
27 
28 #define RTK_URI "http://gareus.org/oss/lv2/meters#"
29 #define RTK_GUI "needle"
30 #define MTR_URI RTK_URI
31 #define LVGL_RESIZEABLE
32 
33 /* meter types */
34 enum MtrType {
35 	MT_BBC = 1,
36 	MT_BM6,
37 	MT_EBU,
38 	MT_DIN,
39 	MT_NOR,
40 	MT_VU,
41 	MT_COR
42 };
43 
44 typedef struct {
45 	RobWidget *rw;
46 
47 	LV2UI_Write_Function write;
48 	LV2UI_Controller     controller;
49 
50 	cairo_surface_t * bg;
51 	cairo_surface_t * adj;
52 	cairo_surface_t * sf_nfo;
53 	unsigned char   * img0;
54 	unsigned char   * img1;
55 	float col[3];
56 
57 	bool naned[2];
58 	float lvl[2];
59 	float cal;
60 	float cal_rad;
61 	bool bbc_s20;
62 	int chn;
63 	enum MtrType type;
64 
65 	float drag_x, drag_y, drag_cal;
66 	int width, height;
67 	int x0, y0;
68 	int x1, y1;
69 
70 	PangoFontDescription* font[2];
71 
72 	/*** pixel design ***/
73 	float scale;
74 	float s_scale;
75 	/* screw area design */
76 	float s_xc;
77 	float s_yc;
78 	float s_w2;
79 	float s_h2;
80 	cairo_rectangle_t screwrect;
81 	cairo_rectangle_t textrect;
82 
83 	/* bbc +20dB */
84 	float bbc_xc;
85 	float bbc_yc;
86 	float bbc_w2;
87 	float bbc_h2;
88 	cairo_rectangle_t bbc_rect;
89 
90 	/* meter size */
91 	float m_width;
92 	float m_height;
93 	float n_height;
94 
95 	/* needle */
96 	float n_xc;
97 	float n_yc;
98 	float m_r0;
99 	float m_r1;
100 
101 	const char *nfo;
102 
103 } MetersLV2UI;
104 
105 #include "gui/meterimage.c"
106 
setup_images(MetersLV2UI * ui)107 static void setup_images (MetersLV2UI* ui) {
108 	ui->bg = render_front_face(ui->type, ui->m_width, ui->m_height);
109 	img2surf((struct MyGimpImage const *)&img_screw, &ui->adj, &ui->img1);
110 }
111 
width_scale(MetersLV2UI * ui)112 static int width_scale(MetersLV2UI* ui) {
113 	switch (ui->type) {
114 		case MT_BBC:
115 		case MT_BM6:
116 			return 1;
117 			break;
118 		default:
119 			return ui->chn;
120 	}
121 }
122 
set_needle_sizes(MetersLV2UI * ui)123 static void set_needle_sizes(MetersLV2UI* ui) {
124 	const float scale = ui->scale;
125 	ui->s_scale = scale;
126 	if (ui->s_scale > 2.0) ui->s_scale = 2.0;
127 
128 	/* screw area design */
129 	ui->s_xc = 150.0 * scale; // was (300.0 * ui->chn)/2.0;
130 	ui->s_yc = 153.0 * scale;
131 	ui->s_w2 = ui->s_h2 =  12.5 * ui->s_scale;
132 	ui->screwrect.x = (ui->s_xc - ui->s_w2) - 2;
133 	ui->screwrect.y = (ui->s_yc - ui->s_w2) - 2;
134 	ui->screwrect.width = ui->screwrect.height = 4 + 2 * ui->s_w2;
135 
136 	ui->textrect.x = (150 + ui->s_w2) * scale;
137 	ui->textrect.y = (153 -     15  ) * scale;
138 	ui->textrect.width = 150;
139 	ui->textrect.height = 30;
140 
141 	/* BBC switch */
142 	ui->bbc_xc = .5 + floor (72.0 * scale);
143 	ui->bbc_yc = .5 + floor (153.0 * scale);
144 	ui->bbc_w2 = floor (20 * ui->s_scale);
145 	ui->bbc_h2 = floor (10 * ui->s_scale);
146 	ui->bbc_rect.x = (ui->bbc_xc - ui->bbc_w2) - 2;
147 	ui->bbc_rect.y = (ui->bbc_yc - ui->bbc_h2) - 2;
148 	ui->bbc_rect.width  = 4 + 2 * ui->bbc_w2;
149 	ui->bbc_rect.height = 4 + 2 * ui->bbc_h2;
150 
151 	/* meter size */
152 	ui->m_width  = rint(300.0 * scale);
153 	ui->m_height = rint(170.0 * scale);
154 	ui->n_height = rint(135.0 * scale); // bottom separator
155 
156 	/* needle */
157 	ui->n_xc =  149.5 * scale;
158 	ui->n_yc =  209.5 * scale;
159 	ui->m_r0 =  180.0 * scale;
160 	ui->m_r1 =   72.0 * scale;
161 
162 	ui->width = ui->m_width * width_scale(ui);
163 	ui->height = ui->m_height;
164 
165 	if (ui->bg) cairo_surface_destroy(ui->bg);
166 	if (ui->font[0]) pango_font_description_free(ui->font[0]);
167 	if (ui->font[1]) pango_font_description_free(ui->font[1]);
168 
169 	ui->bg = render_front_face(ui->type, ui->m_width, ui->m_height);
170 
171 	char fontname[32];
172 	sprintf(fontname, "Sans %dpx", (int)rint(10.0 * ui->scale));
173 	ui->font[0] = pango_font_description_from_string(fontname);
174 	sprintf(fontname, "Sans %dpx", (int)rint(8.0 * ui->scale));
175 	ui->font[1] = pango_font_description_from_string(fontname);
176 
177 	if (ui->sf_nfo) {
178 		cairo_surface_destroy(ui->sf_nfo);
179 		ui->sf_nfo = NULL;
180 	}
181 	if (ui->nfo) {
182 		PangoFontDescription *fd = pango_font_description_from_string("Sans 10px");
183 		create_text_surface2(&ui->sf_nfo,
184 				ui->width, 12,
185 				ui->width - 2, 0,
186 				ui->nfo, fd, 0, 7, c_g30);
187 		pango_font_description_free(fd);
188 	}
189 }
190 
draw_background(MetersLV2UI * ui,cairo_t * cr,float xoff,float yoff)191 static void draw_background (MetersLV2UI* ui, cairo_t* cr, float xoff, float yoff) {
192 	float w =  cairo_image_surface_get_width (ui->bg);
193 	float h =  cairo_image_surface_get_height (ui->bg);
194 
195 	cairo_save(cr);
196 	cairo_scale(cr, ui->m_width / w, ui->m_height / h);
197 	cairo_set_source_surface(cr, ui->bg, xoff * w / ui->m_width, yoff);
198 	cairo_rectangle (cr, xoff * w / ui->m_width, 0, w, h);
199 	cairo_fill(cr);
200 	cairo_restore(cr);
201 
202 	if (ui->sf_nfo) {
203 		cairo_set_source_surface(cr, ui->sf_nfo, 0, ui->m_height - 12);
204 		cairo_paint (cr);
205 	}
206 }
207 
208 
209 /******************************************************************************
210  * some simple maths helpers
211  */
212 
cal2rad(enum MtrType t,float v)213 static float cal2rad(enum MtrType t, float v) {
214 	/* rotate screw  [-30..0]  -> [-M_PI/4 .. M_PI/4] */
215 	return .0837758 * (v + (t == MT_DIN ? 15.0 : 18.0));
216 }
217 
calc_needle_pos(MetersLV2UI * ui,float val,const float xoff,float * const x,float * const y)218 static inline void calc_needle_pos(MetersLV2UI* ui, float val, const float xoff, float * const x, float * const y) {
219 	const float _xc = ui->n_xc + xoff;
220 
221 	if (val < 0.00f) val = 0.00f;
222 	if (val > 1.05f) val = 1.05f;
223 	val = (val - 0.5f) * 1.5708f;
224 
225 	*x = _xc + sinf (val) * ui->m_r0;
226 	*y = ui->n_yc - cosf (val) * ui->m_r0;
227 }
228 
calc_needle_area(MetersLV2UI * ui,float val,const float xoff,cairo_rectangle_t * r)229 static inline void calc_needle_area(MetersLV2UI* ui, float val, const float xoff, cairo_rectangle_t *r) {
230 	const float _xc = ui->n_xc + xoff;
231 
232 	if (val < 0.00f) val = 0.00f;
233 	if (val > 1.05f) val = 1.05f;
234 	val = (val - 0.5f) * 1.5708f;
235 
236 	const float _sf = sinf (val) ;
237 	const float _cf = cosf (val) ;
238 
239 	const float _x0 = _xc + _sf * ui->m_r0;
240 	const float _y0 = ui->n_yc - _cf * ui->m_r0;
241 	const float _x1 = _xc + _sf * ui->m_r1;
242 	const float _y1 = ui->n_yc - _cf * ui->m_r1;
243 
244 	r->x      = MIN(_x0, _x1) - 3.0 * ui->scale;
245 	r->y      = MIN(_y0, _y1) - 3.0 * ui->scale;
246 	r->width  = MAX(_x0 - _x1, _x1 - _x0) + 6.0 * ui->scale;
247 	r->height = MAX(0, (ui->n_height - r->y)) + 6.0 * ui->scale;
248 }
249 
meter_deflect(int type,float v)250 static float meter_deflect(int type, float v) {
251 	switch(type) {
252 		case MT_VU:
253 			return 5.6234149f * v;
254 		case MT_BBC:
255 		case MT_BM6:
256 		case MT_EBU:
257 			v *= 3.17f;
258 			if (v < 0.1f) return v * 0.855f;
259 			else return 0.3f * logf (v) + 0.77633f;
260 		case MT_DIN:
261 			v = sqrtf (sqrtf (2.002353f * v)) - 0.1885f;
262 			return (v < 0.0f) ? 0.0f : v;
263 		case MT_NOR:
264 			if (v < 1e-5) return 0;
265 			return .4166666f * log10(v) + 1.125f; // (20.0/48.0) *log(v) + (54/48.0)  -> -54dBFS ^= 0, -12dB ^= 1.0
266 		case MT_COR:
267 			return 0.5f * (1.0f + v);
268 		default:
269 			return 0;
270 	}
271 }
272 
273 /******************************************************************************
274  * Drawing helpers
275  */
276 
draw_needle(MetersLV2UI * ui,cairo_t * cr,float val,const float xoff,const float * const col,const float lw)277 static void draw_needle (MetersLV2UI* ui, cairo_t* cr, float val,
278 		const float xoff, const float * const col, const float lw) {
279 	cairo_save(cr);
280 
281 	/* needle area */
282 	cairo_rectangle (cr, xoff, 0, ui->m_width, ui->n_height);
283 	cairo_clip (cr);
284 
285 	/* draw needle */
286 	const float _xc = ui->n_xc + xoff;
287 	float px, py;
288 
289 	calc_needle_pos(ui, val, xoff, &px, &py);
290 
291 	cairo_new_path (cr);
292 	cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
293 	cairo_move_to (cr, _xc, ui->n_yc);
294 	cairo_line_to (cr, px, py);
295 	CairoSetSouerceRGBA(col);
296 	cairo_set_line_width (cr, lw * ui->scale);
297 	cairo_stroke (cr);
298 
299 	cairo_restore(cr);
300 }
301 
302 #define NANED(X,Y, COL) \
303 	cairo_save(cr); \
304 	rounded_rectangle (cr, (X) - 30 * ui->scale, (Y) - 5 * ui->scale, 60 * ui->scale,  20 * ui->scale, 4*ui->scale); \
305 	CairoSetSouerceRGBA(COL); \
306 	cairo_fill_preserve(cr); \
307 	cairo_set_line_width (cr, .75 * ui->scale); \
308 	CairoSetSouerceRGBA(c_gry); \
309 	cairo_stroke(cr); \
310 	write_text_full(cr, "NaN", ui->font[0], (X), (Y) + 5 * ui->scale, 0, 2, c_wht); \
311 	cairo_restore(cr);
312 
313 
314 /******************************************************************************
315  * main drawing function
316  */
expose_event(RobWidget * handle,cairo_t * cr,cairo_rectangle_t * ev)317 static bool expose_event(RobWidget* handle, cairo_t* cr, cairo_rectangle_t* ev) {
318 	MetersLV2UI* ui = (MetersLV2UI*)GET_HANDLE(handle);
319 	float const * col;
320 	cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height);
321 	cairo_clip (cr);
322 
323 	cairo_rectangle (cr, 2.5, 2.5, ui->x1 - 5, ui->y1 - 5);
324 	cairo_set_source_rgb (cr, .55, .55, .55);
325 	cairo_set_line_width (cr, 6.0);
326 	cairo_stroke(cr);
327 
328 	cairo_translate (cr, ui->x0, ui->y0);
329 
330 	switch(ui->type) {
331 		case MT_VU:
332 			col = c_blk;
333 			break;
334 		default:
335 			col = c_wht;
336 			break;
337 	}
338 
339 	if (ui->type == MT_COR) {
340 		draw_background (ui, cr, 0, 0);
341 		draw_needle (ui, cr, ui->lvl[0], 0, col, 2.0);
342 		return TRUE;
343 	}
344 	else if (ui->type == MT_BBC && ui->chn == 2) {
345 		draw_background (ui, cr, 0, 0);
346 		if (ui->naned[0]) { NANED(ui->m_width/2, ui->height*2/3 - 20 * ui->scale, c_red); }
347 		if (ui->naned[1]) { NANED(ui->m_width/2, ui->height*2/3 +  2 * ui->scale, c_grn); }
348 		draw_needle (ui, cr, ui->lvl[1], 0, c_grn, 2.0);
349 		draw_needle (ui, cr, ui->lvl[0], 0, c_red, 2.0);
350 	}
351 	else if (ui->type == MT_BM6 && ui->chn == 2) {
352 		draw_background (ui, cr, 0, 0);
353 		if (ui->naned[0]) { NANED(ui->m_width/2, ui->height*2/3 - 20 * ui->scale, c_red); }
354 		if (ui->naned[1]) { NANED(ui->m_width/2, ui->height*2/3 +  2 * ui->scale, c_grn); }
355 		draw_needle (ui, cr, ui->lvl[1], 0, c_nyl, 2.0); // XXX
356 		draw_needle (ui, cr, ui->lvl[0], 0, c_wht, 2.0);
357 	} else {
358 		int c;
359 		for (c=0; c < ui->chn; ++c) {
360 			draw_background (ui, cr, ui->m_width * c, 0);
361 			if (ui->naned[c]) { NANED(ui->m_width * c + ui->m_width/2, ui->height*2/3, c_red); }
362 			draw_needle (ui, cr, ui->lvl[c], ui->m_width * c, col, 1.4);
363 		}
364 	}
365 
366 	/* draw callibration text */
367 	if (rect_intersect(ev , &ui->textrect) && (ui->drag_x >= 0 || ui->drag_y >=0)) {
368 		char buf[48];
369 		/* default gain -18.0dB in meters.cc, except DIN: -15dB (deflection) */
370 		switch (ui->type) {
371 			case MT_VU:
372 				sprintf(buf, "0 VU = %.1f dBFS", -36 - ui->cal);
373 				break;
374 			case MT_BBC:
375 			case MT_BM6:
376 				sprintf(buf, " '4' = %.1f dBFS", -36 - ui->cal);
377 				break;
378 			case MT_DIN:
379 				/* specs: -3dBu = '-9' ^= -18 dbFS - so these are eqivalent: */
380 				//sprintf(buf, " '-6' = %.1f dBFS",  -30 - ui->cal); // no '-6' label
381 				//sprintf(buf, "'50%%' = %.1f dBFS", -30 - ui->cal); // mmh
382 				//sprintf(buf, " 0dBu = %.1f dBFS",  -30 - ui->cal);
383 				sprintf(buf, " '-9' = %.1f dBFS",  -33 - ui->cal);
384 				break;
385 			case MT_EBU:
386 			case MT_NOR:
387 				sprintf(buf, " 'TEST' = %.1f dBFS", -36 - ui->cal);
388 				break;
389 			default:
390 				/* not reached */
391 				break;
392 		}
393 
394 		write_text_full(cr, buf, ui->font[0], ui->s_xc + ui->s_w2 + 8, ui->s_yc, 0, 3, c_wht);
395 	}
396 
397 	/* draw callibration screw */
398 	if (rect_intersect(ev , &ui->screwrect)) {
399 		cairo_save(cr);
400 		cairo_translate (cr, ui->s_xc, ui->s_yc);
401 		cairo_rotate (cr, ui->cal_rad);
402 		cairo_translate (cr, -ui->s_w2, -ui->s_h2);
403 		cairo_scale(cr, ui->s_scale, ui->s_scale);
404 		cairo_set_source_surface(cr, ui->adj, 0, 0);
405 		cairo_rectangle (cr, 0, 0, 25.0, 25.0 /* 2.0 * ui->s_w2 / ui->scale, 2.0 * ui->s_h2 / ui->scale */);
406 		cairo_fill(cr);
407 		cairo_restore(cr);
408 
409 		cairo_save(cr);
410 		cairo_translate (cr, ui->s_xc, ui->s_yc);
411 		CairoSetSouerceRGBA(c_scr);
412 		cairo_arc(cr, 0, 0, ui->s_w2, 0, 2 * M_PI);
413 		cairo_set_line_width (cr, 1.0);
414 		cairo_stroke(cr);
415 		cairo_restore(cr);
416 	}
417 
418 	/* draw +20db switch */
419 	if (ui->type == MT_BM6 && rect_intersect (ev, &ui->bbc_rect)) {
420 		cairo_save(cr);
421 		cairo_translate (cr, ui->bbc_xc - ui->bbc_w2, ui->bbc_yc - ui->bbc_h2);
422 		cairo_rectangle (cr, 0, 0, 2 * ui->bbc_w2, 2 * ui->bbc_h2);
423 		cairo_clip (cr);
424 
425 		int sw_x1 = 2 * ui->bbc_w2 - 2;
426 		int sw_y1 = 2 * ui->bbc_h2 - 3;
427 		int sw_ww = floor (7 * ui->s_scale);
428 
429 
430 		if (ui->bbc_s20) {
431 			cairo_set_source_rgba (cr, .7, .1, .1, .7);
432 			cairo_rectangle (cr, 0, 0, 2 * ui->bbc_w2, 2 * ui->bbc_h2);
433 		} else {
434 			cairo_set_source_rgba (cr, .7, .1, .1, .7);
435 			cairo_rectangle (cr, 0, 0, sw_ww + 4, 2 * ui->bbc_h2);
436 			cairo_fill (cr);
437 			cairo_set_source_rgba (cr, .1, .1, .1, .7);
438 			cairo_rectangle (cr, sw_ww + 4, 0, 2 * ui->bbc_w2 - sw_ww - 4, 2 * ui->bbc_h2);
439 		}
440 		cairo_fill (cr);
441 
442 		/* schieber */
443 		cairo_save(cr);
444 		if (ui->bbc_s20) {
445 			cairo_translate (cr, sw_x1 - sw_ww, 1);
446 		} else {
447 			cairo_translate (cr, 2, 1);
448 		}
449 
450 		cairo_rectangle (cr, 0, 0, sw_ww, sw_y1);
451 		cairo_set_source_rgba (cr, 1, .0, .0, .7);
452 		cairo_fill (cr);
453 
454 		cairo_set_line_width (cr, 1);
455 		cairo_set_source_rgba (cr, 1, .2, .2, 1);
456 
457 		cairo_move_to (cr, 0, 0);
458 		cairo_rel_line_to (cr, sw_ww, 0);
459 		cairo_stroke (cr);
460 		cairo_move_to (cr, sw_ww, 0);
461 		cairo_rel_line_to (cr, 0, sw_y1);
462 		cairo_stroke (cr);
463 
464 		cairo_set_source_rgba (cr, .5, .0, .0, 1);
465 		cairo_move_to (cr, 0, 0);
466 		cairo_rel_line_to (cr, 0, sw_y1);
467 		cairo_stroke (cr);
468 		cairo_move_to (cr, 0, sw_y1);
469 		cairo_rel_line_to (cr, sw_ww, 0);
470 		cairo_stroke (cr);
471 		cairo_restore(cr);
472 
473 		if (ui->bbc_s20) {
474 			cairo_save (cr);
475 			write_text_full (cr, "S+20", ui->font[1], ui->bbc_w2 - sw_ww / 2, ui->bbc_h2, 0, 2, c_wht);
476 			cairo_restore (cr);
477 		}
478 
479 		cairo_rectangle (cr, 0, 0, 2 * ui->bbc_w2, 2 * ui->bbc_h2);
480 		CairoSetSouerceRGBA (c_scr);
481 		cairo_set_line_width (cr, 1.0);
482 		cairo_stroke (cr);
483 
484 		cairo_set_line_width (cr, 1.5);
485 		cairo_set_source_rgba (cr, .1, .1, .1, .5);
486 		cairo_move_to (cr, 1, 0);
487 		cairo_rel_line_to (cr, 0, 2 * ui->bbc_h2 - 1);
488 		cairo_stroke (cr);
489 		cairo_move_to (cr, 0, 2 * ui->bbc_h2 - 1);
490 		cairo_rel_line_to (cr, 2 * ui->bbc_w2 - 1, 0);
491 		cairo_stroke (cr);
492 
493 		cairo_restore(cr);
494 	}
495 
496 	return TRUE;
497 }
498 
499 /******************************************************************************
500  * UI event handling
501  */
502 
503 /* calibration screw drag/drop handling */
mousedown(RobWidget * handle,RobTkBtnEvent * event)504 static RobWidget* mousedown(RobWidget* handle, RobTkBtnEvent *event) {
505 	MetersLV2UI* ui = (MetersLV2UI*)GET_HANDLE(handle);
506 
507 	if (event->state & ROBTK_MOD_CTRL) {
508 		robwidget_resize_toplevel(ui->rw, 300 * width_scale(ui), 170);
509 		return NULL;
510 	}
511 
512 	if (ui->naned[0]) { ui->naned[0] = FALSE; queue_draw(ui->rw); }
513 	if (ui->naned[1]) { ui->naned[1] = FALSE; queue_draw(ui->rw); }
514 	if (ui->type == MT_BM6
515 	    && event->x > ui->bbc_xc - ui->bbc_w2
516 	    && event->x < ui->bbc_xc + ui->bbc_w2
517 	    && event->y > ui->bbc_yc - ui->bbc_h2
518 	    && event->y < ui->bbc_yc + ui->bbc_h2
519 		 )
520 	{
521 		/* Toggle BBC +20dB Side switch */
522 		float onoff = ui->bbc_s20 ? 0 : 1;
523 		ui->write(ui->controller, 7, sizeof(float), 0, (const void*) &onoff);
524 		return NULL;
525 	}
526 
527 	if (   event->x < ui->s_xc - ui->s_w2
528 			|| event->x > ui->s_xc + ui->s_w2
529 			|| event->y < ui->s_yc - ui->s_h2
530 			|| event->y > ui->s_yc + ui->s_h2
531 			) {
532 		/* outside of adj-screw area */
533 		return NULL;
534 	}
535 
536 	if (event->state & ROBTK_MOD_SHIFT) {
537 		/* shift-click -> reset to default */
538 		switch(ui->type) {
539 			case MT_VU: ui->cal = -22; break;
540 			case MT_DIN: ui->cal = -15; break;
541 			default: ui->cal = -18; break;
542 		}
543 		ui->write(ui->controller, 0, sizeof(float), 0, (const void*) &ui->cal);
544 		ui->cal_rad = cal2rad(ui->type, ui->cal);
545 		queue_draw(ui->rw);
546 		return NULL;
547 	}
548 
549 	ui->drag_x = event->x;
550 	ui->drag_y = event->y;
551 	ui->drag_cal = ui->cal;
552 	queue_draw(ui->rw);
553 	return handle;
554 }
555 
556 /* stereo-phase correlation - resize to 100% only */
mousedown_cor(RobWidget * handle,RobTkBtnEvent * event)557 static RobWidget* mousedown_cor(RobWidget* handle, RobTkBtnEvent *event) {
558 	MetersLV2UI* ui = (MetersLV2UI*)GET_HANDLE(handle);
559 	if (event->state & ROBTK_MOD_CTRL) {
560 		robwidget_resize_toplevel(ui->rw, 300 * width_scale(ui), 170);
561 	}
562 	return NULL;
563 }
564 
565 
mouseup(RobWidget * handle,RobTkBtnEvent * event)566 static RobWidget* mouseup(RobWidget* handle, RobTkBtnEvent *event) {
567 	MetersLV2UI* ui = (MetersLV2UI*)GET_HANDLE(handle);
568 	ui->drag_x = ui->drag_y = -1;
569 	queue_draw(ui->rw);
570 	return NULL;
571 }
572 
mousemove(RobWidget * handle,RobTkBtnEvent * event)573 static RobWidget* mousemove(RobWidget* handle, RobTkBtnEvent *event) {
574 	MetersLV2UI* ui = (MetersLV2UI*)GET_HANDLE(handle);
575 	if (ui->drag_x < 0 || ui->drag_y < 0) return NULL;
576 
577 	const float diff = rint(((event->x - ui->drag_x) - (event->y - ui->drag_y)) / 5.0 ) * .5;
578 	float cal = ui->drag_cal + diff;
579 	if (cal < -30.0) cal = -30.0;
580 	if (cal > 0.0) cal = 0.0;
581 
582 	//printf("Mouse move.. %f %f -> %f   (%f -> %f)\n", event->x, event->y, diff, ui->drag_cal, cal);
583 	ui->write(ui->controller, 0, sizeof(float), 0, (const void*) &cal);
584 	ui->cal = cal;
585 	ui->cal_rad = cal2rad(ui->type, ui->cal);
586 	queue_draw(ui->rw);
587 
588 	return handle;
589 }
590 
591 
592 /******************************************************************************
593  * widget hackery
594  */
595 
596 static void
size_request(RobWidget * rw,int * w,int * h)597 size_request(RobWidget* rw, int *w, int *h) {
598 	MetersLV2UI* ui = (MetersLV2UI*)GET_HANDLE(rw);
599 	*w = 300 * .75 * width_scale(ui);
600 	*h = 170 * .75;
601 }
602 
603 static void
size_default(RobWidget * rw,int * w,int * h)604 size_default(RobWidget* rw, int *w, int *h) {
605 	MetersLV2UI* ui = (MetersLV2UI*)GET_HANDLE(rw);
606 	*w = 300 * width_scale(ui);
607 	*h = 170;
608 }
609 
610 static void
size_limit(RobWidget * rw,int * w,int * h)611 size_limit(RobWidget* rw, int *w, int *h) {
612 	MetersLV2UI* ui = (MetersLV2UI*)GET_HANDLE(rw);
613 	int dflw, dflh;
614 	size_default(rw, &dflw, &dflh);
615 	float scale = MIN(*w/(float)dflw, *h/(float)dflh);
616 	if (scale < .5 ) scale = .5;
617 	if (scale > 3.5 ) scale = 3.5;
618 	ui->scale  = scale;
619 	set_needle_sizes(ui); // sets ui->width, ui->height
620 	ui->x0 = (*w - ui->width) / 2;
621 	ui->y0 = (*h - ui->height) / 2;
622 	ui->x1 = *w;
623 	ui->y1 = *h;
624 	robwidget_set_size(rw, *w, *h);
625 	queue_draw(rw);
626 }
627 
628 /******************************************************************************
629  * LV2 callbacks
630  */
631 
ui_enable(LV2UI_Handle handle)632 static void ui_enable(LV2UI_Handle handle) { }
ui_disable(LV2UI_Handle handle)633 static void ui_disable(LV2UI_Handle handle) { }
634 
635 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)636 instantiate(
637 		void* const               ui_toplevel,
638 		const LV2UI_Descriptor*   descriptor,
639 		const char*               plugin_uri,
640 		const char*               bundle_path,
641 		LV2UI_Write_Function      write_function,
642 		LV2UI_Controller          controller,
643 		RobWidget**               widget,
644 		const LV2_Feature* const* features)
645 {
646 	MetersLV2UI* ui = (MetersLV2UI*)calloc(1, sizeof(MetersLV2UI));
647 	*widget = NULL;
648 
649 	if (!ui) {
650 		fprintf (stderr, "meters.lv2: out of memory.\n");
651 		return NULL;
652 	}
653 
654 	if      (!strcmp(plugin_uri, MTR_URI "VUmono"))    { ui->chn = 1; ui->type = MT_VU; }
655 	else if (!strcmp(plugin_uri, MTR_URI "VUstereo"))  { ui->chn = 2; ui->type = MT_VU; }
656 	else if (!strcmp(plugin_uri, MTR_URI "BBCmono"))   { ui->chn = 1; ui->type = MT_BBC; }
657 	else if (!strcmp(plugin_uri, MTR_URI "BBCstereo")) { ui->chn = 2; ui->type = MT_BBC; }
658 	else if (!strcmp(plugin_uri, MTR_URI "BBCM6"))     { ui->chn = 2; ui->type = MT_BM6; }
659 	else if (!strcmp(plugin_uri, MTR_URI "EBUmono"))   { ui->chn = 1; ui->type = MT_EBU; }
660 	else if (!strcmp(plugin_uri, MTR_URI "EBUstereo")) { ui->chn = 2; ui->type = MT_EBU; }
661 	else if (!strcmp(plugin_uri, MTR_URI "DINmono"))   { ui->chn = 1; ui->type = MT_DIN; }
662 	else if (!strcmp(plugin_uri, MTR_URI "DINstereo")) { ui->chn = 2; ui->type = MT_DIN; }
663 	else if (!strcmp(plugin_uri, MTR_URI "NORmono"))   { ui->chn = 1; ui->type = MT_NOR; }
664 	else if (!strcmp(plugin_uri, MTR_URI "NORstereo")) { ui->chn = 2; ui->type = MT_NOR; }
665 	else if (!strcmp(plugin_uri, MTR_URI "COR"))       { ui->chn = 1; ui->type = MT_COR; }
666 
667 	if (ui->type == 0) {
668 		free(ui);
669 		return NULL;
670 	}
671 
672 	ui->write      = write_function;
673 	ui->controller = controller;
674 	ui->lvl[0]     = ui->lvl[1] = 0;
675 	ui->naned[0]   = ui->naned[1] = FALSE;
676 	ui->cal        = -18.0;
677 	ui->cal_rad    = cal2rad(ui->type, ui->cal);
678 	ui->bbc_s20    = false;
679 	ui->bg         = NULL;
680 	ui->adj        = NULL;
681 	ui->sf_nfo     = NULL;
682 	ui->img0       = NULL;
683 	ui->drag_x     = ui->drag_y = -1;
684 	ui->scale      = 1.0;
685 	ui->font[0]    = NULL;
686 	ui->font[1]    = NULL;
687 
688 	ui->nfo = robtk_info(ui_toplevel);
689 	set_needle_sizes(ui);
690 
691 	setup_images(ui);
692 
693 	ui->rw = robwidget_new(ui);
694 	robwidget_make_toplevel(ui->rw, ui_toplevel);
695 	ROBWIDGET_SETNAME(ui->rw, "needle");
696 
697 	robwidget_set_expose_event(ui->rw, expose_event);
698 	robwidget_set_size_request(ui->rw, size_request);
699 	robwidget_set_size_limit(ui->rw, size_limit);
700 	robwidget_set_size_default(ui->rw, size_default);
701 
702 	if (ui->type != MT_COR) {
703 		robwidget_set_mousedown(ui->rw, mousedown);
704 		robwidget_set_mouseup(ui->rw, mouseup);
705 		robwidget_set_mousemove(ui->rw, mousemove)
706 	} else {
707 		robwidget_set_mousedown(ui->rw, mousedown_cor);
708 	}
709 
710 	*widget = ui->rw;
711 
712 	return ui;
713 }
714 
715 static enum LVGLResize
plugin_scale_mode(LV2UI_Handle handle)716 plugin_scale_mode(LV2UI_Handle handle)
717 {
718 	return LVGL_LAYOUT_TO_FIT;
719 }
720 
721 static void
cleanup(LV2UI_Handle handle)722 cleanup(LV2UI_Handle handle)
723 {
724 	MetersLV2UI* ui = (MetersLV2UI*)handle;
725 	cairo_surface_destroy(ui->sf_nfo);
726 	cairo_surface_destroy(ui->bg);
727 	cairo_surface_destroy(ui->adj);
728 	pango_font_description_free(ui->font[0]);
729 	pango_font_description_free(ui->font[1]);
730 	robwidget_destroy(ui->rw);
731 	free(ui->img0);
732 	free(ui->img1);
733 	free(ui);
734 }
735 
736 
737 static const void*
extension_data(const char * uri)738 extension_data(const char* uri)
739 {
740 	return NULL;
741 }
742 
743 
744 /******************************************************************************
745  * backend communication
746  */
747 
748 #define MIN2(A,B) ( (A) < (B) ? (A) : (B) )
749 #define MAX2(A,B) ( (A) > (B) ? (A) : (B) )
750 #define MIN3(A,B,C) (  (A) < (B)  ? MIN2 (A,C) : MIN2 (B,C) )
751 #define MAX3(A,B,C) (  (A) > (B)  ? MAX2 (A,C) : MAX2 (B,C) )
752 
invalidate_area(MetersLV2UI * ui,int c,float oldval,float newval)753 static void invalidate_area(MetersLV2UI* ui, int c, float oldval, float newval) {
754 	if (!ui->naned[c] && (isnan(newval) || isinf(newval)))  {
755 		ui->naned[c] = TRUE;
756 		queue_draw(ui->rw);
757 	}
758 	if (oldval < 0.00f) oldval = 0.00f;
759 	if (oldval > 1.05f) oldval = 1.05f;
760 	if (newval < 0.00f) newval = 0.00f;
761 	if (newval > 1.05f) newval = 1.05f;
762 
763 	if (rint(newval * 540) == rint(oldval * 540)) {
764 		return;
765 	}
766 
767 	float xoff = ui->m_width * c;
768 	if (c == 1 && (ui->type == MT_BBC || ui->type == MT_BM6)) {
769 		xoff = 0;
770 	}
771 	cairo_rectangle_t r1, r2;
772 	calc_needle_area(ui, oldval, xoff, &r1);
773 	calc_needle_area(ui, newval, xoff, &r2);
774 	rect_combine(&r1, &r2, &r1);
775 	queue_tiny_area(ui->rw, ui->x0 + r1.x, ui->y0 + r1.y, r1.width, r1.height);
776 }
777 
778 static void
port_event(LV2UI_Handle handle,uint32_t port_index,uint32_t buffer_size,uint32_t format,const void * buffer)779 port_event(LV2UI_Handle handle,
780            uint32_t     port_index,
781            uint32_t     buffer_size,
782            uint32_t     format,
783            const void*  buffer)
784 {
785 	MetersLV2UI* ui = (MetersLV2UI*)handle;
786 
787 	if ( format != 0 ) { return; }
788 
789 	if (port_index == 3) {
790 		float nl = meter_deflect(ui->type, *(float *)buffer);
791 		invalidate_area(ui, 0, ui->lvl[0], nl);
792 		ui->lvl[0] = nl;
793 	} else
794 	if (port_index == 6) {
795 		float nl = meter_deflect(ui->type, *(float *)buffer);
796 		invalidate_area(ui, 1, ui->lvl[1], nl);
797 		ui->lvl[1] = nl;
798 	} else
799 	if (port_index == 0) {
800 		ui->cal = *(float *)buffer;
801 		ui->cal_rad = cal2rad(ui->type, ui->cal);
802 		queue_draw(ui->rw);
803 	} else
804 	if (port_index == 7 && ui->type == MT_BM6) {
805 		ui->bbc_s20 = *(float *)buffer > 0;
806 		queue_draw(ui->rw);
807 	}
808 }
809