1 /* spectrum analyzer LV2 GUI
2 *
3 * Copyright 2012-2015 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
16 * along with this program; if not, write to the Free Software Foundation,
17 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
19
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <math.h>
23 #include <string.h>
24 #include <assert.h>
25
26 #define RTK_URI "http://gareus.org/oss/lv2/meters#"
27 #define RTK_GUI "dpmui"
28 #define MTR_URI RTK_URI
29
30 #define LVGL_RESIZEABLE
31
32 #define GM_TOP (ui->display_freq ? 4.5f : 12.5 + GM_PEAK)
33 #define GM_LEFT (ui->gm_left)
34 #define GM_GIRTH (ui->gm_girth)
35 #define GM_WIDTH (ui->gm_width)
36
37 #define GM_MINH (396.0f)
38
39 //#define GM_HEIGHT (ui->display_freq ? 400.0f:460.0f)
40 #define GM_HEIGHT (ui->height)
41 #define GM_SCALE (GM_TXT - GM_TOP - (ui->display_freq ? 8.5f : 12.5))
42 #define GM_PEAK ceil(8 + 9.f * ui->scale)
43 #define FQ_ANN ceil(51.f * ui->scale)
44 #define FQ_WIDTH (13 + FQ_ANN)
45 #define AN_HEIGHT ceil(46.f * ui->scale)
46 #define AN_WIDTH ceil(32.f * ui->scale)
47 #define GM_TXT (GM_HEIGHT - (ui->display_freq ? FQ_ANN : GM_PEAK))
48
49 #define MA_WIDTH ceil(30.0f * ui->scale)
50
51 #define MAX_CAIRO_PATH 32
52 #define MAX_METERS 31
53
54 #define TOF ((GM_TOP ) / GM_HEIGHT)
55 #define BOF ((GM_TOP + GM_SCALE) / GM_HEIGHT)
56 #define YVAL(x) ((GM_TOP + GM_SCALE - (x)) / GM_HEIGHT)
57 #define YPOS(x) (GM_TOP + GM_SCALE - (x))
58
59 #define UINT_TO_RGB(u,r,g,b) { (*(r)) = ((u)>>16)&0xff; (*(g)) = ((u)>>8)&0xff; (*(b)) = (u)&0xff; }
60 #define UINT_TO_RGBA(u,r,g,b,a) { UINT_TO_RGB(((u)>>8),r,g,b); (*(a)) = (u)&0xff; }
61
62 /* val: .05 .. 15 1/s <> 0..100 */
63 #define RESPSCALE(X) ((X) > 0.05 ? rint(400.0 * (1.3f + log10f(X)) )/ 10.0 : 0)
64 #define INV_RESPSCALE(X) powf(10, (X) * .025f - 1.3f)
65
66 static const char *freq_table [] = {
67 " 25 Hz", "31.5 Hz", " 40 Hz",
68 " 50 Hz", " 63 Hz", " 80 Hz",
69 "100 Hz", "125 Hz", "160 Hz",
70 "200 Hz", "250 Hz", "315 Hz",
71 "400 Hz", "500 Hz", "630 Hz",
72 "800 Hz", " 1 kHz", "1250 Hz",
73 "1.6 kHz", " 2 kHz", "2.5 kHz",
74 "3150 Hz", " 4 kHz", " 5 kHz",
75 "6.3 kHz", " 8 kHz", "10 kHz",
76 "12.5 kHz", "16 kHz", "20 kHz"
77 };
78
79 typedef struct {
80 RobWidget *rw;
81
82 LV2UI_Write_Function write;
83 LV2UI_Controller controller;
84
85 RobWidget* m_box;
86 RobWidget* c_box;
87 RobWidget* m0;
88 RobTkScale* fader;
89 RobTkLbl* lbl_speed;
90 RobTkCBtn* btn_peaks;
91 RobTkDial* spn_speed;
92 RobTkSep* sep_h0;
93
94 cairo_surface_t* sf[MAX_METERS];
95 cairo_surface_t* an[MAX_METERS];
96 cairo_surface_t* ma[2];
97 cairo_surface_t* dial;
98 cairo_pattern_t* mpat;
99 PangoFontDescription *font[4];
100
101 float val[MAX_METERS];
102 int val_def[MAX_METERS];
103 int val_vis[MAX_METERS];
104
105 float peak_val[MAX_METERS];
106 int peak_def[MAX_METERS];
107 int peak_vis[MAX_METERS];
108
109 bool disable_signals;
110 float gain;
111 uint32_t num_meters;
112 bool display_freq;
113 bool reset_toggle;
114 int initialize;
115 bool metrics_changed;
116 bool size_changed;
117 bool show_peaks;
118 bool show_peaks_changed;
119 uint32_t misc_state;
120
121 float cache_sf;
122 float cache_ma;
123 int highlight;
124
125 float gm_width;
126 float gm_girth;
127 float gm_left;
128 int min_width;
129 int cur_width;
130
131 int width;
132 int height;
133
134 float _min_w;
135 float _min_h;
136
137 float c_txt[4];
138 float c_bgr[4];
139 float scale;
140
141 } SAUI;
142
143 static void invalidate_meter(SAUI* ui, int mtr, float val, float peak);
144
145 /******************************************************************************
146 * meter deflection
147 */
148
149 static inline float
log_meter(float db)150 log_meter (float db)
151 {
152 float def = 0.0f; /* Meter deflection %age */
153
154 if (db < -70.0f) {
155 def = 0.0f;
156 } else if (db < -60.0f) {
157 def = (db + 70.0f) * 0.25f;
158 } else if (db < -50.0f) {
159 def = (db + 60.0f) * 0.5f + 2.5f;
160 } else if (db < -40.0f) {
161 def = (db + 50.0f) * 0.75f + 7.5f;
162 } else if (db < -30.0f) {
163 def = (db + 40.0f) * 1.5f + 15.0f;
164 } else if (db < -20.0f) {
165 def = (db + 30.0f) * 2.0f + 30.0f;
166 } else if (db < 6.0f) {
167 def = (db + 20.0f) * 2.5f + 50.0f;
168 } else {
169 def = 115.0f;
170 }
171
172 /* 115 is the deflection %age that would be
173 when db=6.0. this is an arbitrary
174 endpoint for our scaling.
175 */
176
177 return (def/115.0f);
178 }
179
deflect(SAUI * ui,float val)180 static int deflect(SAUI* ui, float val) {
181 int lvl = rint(GM_SCALE * log_meter(val));
182 if (lvl < 2) lvl = 2;
183 if (lvl >= GM_SCALE) lvl = GM_SCALE;
184 return lvl;
185 }
186
187 /******************************************************************************
188 * Drawing
189 */
190
191 static void render_meter(SAUI*, int, int, int, int, int);
192
193 enum {
194 FONT_S06 = 0,
195 FONT_S08,
196 FONT_M07,
197 FONT_M08
198 };
199
reinitialize_font_cache(SAUI * ui)200 static void reinitialize_font_cache(SAUI* ui) {
201 char fontname[24];
202 for (int i=0; i < 4; ++i) {
203 if (ui->font[i]) {
204 pango_font_description_free(ui->font[i]);
205 }
206 }
207
208 sprintf (fontname, "Sans %dpx", (int)floor(10 * ui->scale));
209 ui->font[FONT_S08] = pango_font_description_from_string(fontname);
210 sprintf (fontname, "Sans %dpx", (int)floor(8 * ui->scale));
211 ui->font[FONT_S06] = pango_font_description_from_string(fontname);
212 sprintf (fontname, "Mono %dpx", (int)floor(9 * ui->scale));
213 ui->font[FONT_M07] = pango_font_description_from_string(fontname);
214 sprintf (fontname, "Mono %dpx", (int)floor(10 * ui->scale));
215 ui->font[FONT_M08] = pango_font_description_from_string(fontname);
216 }
217
218
create_meter_pattern(SAUI * ui)219 static void create_meter_pattern(SAUI* ui) {
220 const int width = GM_WIDTH;
221 const int height = GM_HEIGHT;
222
223 if (ui->mpat) cairo_pattern_destroy(ui->mpat);
224 int clr[12];
225 float stp[5];
226
227 stp[4] = deflect(ui, 0);
228 stp[3] = deflect(ui, -3);
229 stp[2] = deflect(ui, -9);
230 stp[1] = deflect(ui,-18);
231 stp[0] = deflect(ui,-40);
232
233 if (ui->display_freq) {
234 clr[ 0]=0x004488ff; clr[ 1]=0x1188bbff;
235 clr[ 2]=0x228888ff; clr[ 3]=0x00bb00ff;
236 } else {
237 clr[ 0]=0x008844ff; clr[ 1]=0x009922ff;
238 clr[ 2]=0x00aa00ff; clr[ 3]=0x00bb00ff;
239 }
240 clr[ 4]=0x00ff00ff; clr[ 5]=0x00ff00ff;
241 clr[ 6]=0xfff000ff; clr[ 7]=0xfff000ff;
242 clr[ 8]=0xff8000ff; clr[ 9]=0xff8000ff;
243 clr[10]=0xff0000ff; clr[11]=0xff0000ff;
244
245 guint8 r,g,b,a;
246 const double onep = 1.0 / (double) GM_SCALE;
247 const double softT = 2.0 / (double) GM_SCALE;
248 const double softB = 2.0 / (double) GM_SCALE;
249
250 cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
251
252 cairo_pattern_add_color_stop_rgb (pat, .0, .0 , .0, .0);
253 cairo_pattern_add_color_stop_rgb (pat, TOF - onep, .0 , .0, .0);
254 cairo_pattern_add_color_stop_rgb (pat, TOF, .5 , .5, .5);
255
256 // top/clip
257 UINT_TO_RGBA (clr[11], &r, &g, &b, &a);
258 cairo_pattern_add_color_stop_rgb (pat, TOF + onep,
259 r/255.0, g/255.0, b/255.0);
260
261 // -0dB
262 UINT_TO_RGBA (clr[10], &r, &g, &b, &a);
263 cairo_pattern_add_color_stop_rgb (pat, YVAL(stp[4]) - softT,
264 r/255.0, g/255.0, b/255.0);
265 UINT_TO_RGBA (clr[9], &r, &g, &b, &a);
266 cairo_pattern_add_color_stop_rgb (pat, YVAL(stp[4]) + softB,
267 r/255.0, g/255.0, b/255.0);
268
269 // -3dB || -2dB
270 UINT_TO_RGBA (clr[8], &r, &g, &b, &a);
271 cairo_pattern_add_color_stop_rgb (pat, YVAL(stp[3]) - softT,
272 r/255.0, g/255.0, b/255.0);
273 UINT_TO_RGBA (clr[7], &r, &g, &b, &a);
274 cairo_pattern_add_color_stop_rgb (pat, YVAL(stp[3]) + softB,
275 r/255.0, g/255.0, b/255.0);
276
277 // -9dB
278 UINT_TO_RGBA (clr[6], &r, &g, &b, &a);
279 cairo_pattern_add_color_stop_rgb (pat, YVAL(stp[2]) - softT,
280 r/255.0, g/255.0, b/255.0);
281 UINT_TO_RGBA (clr[5], &r, &g, &b, &a);
282 cairo_pattern_add_color_stop_rgb (pat, YVAL(stp[2]) + softB,
283 r/255.0, g/255.0, b/255.0);
284
285 // -18dB
286 UINT_TO_RGBA (clr[4], &r, &g, &b, &a);
287 cairo_pattern_add_color_stop_rgb (pat, YVAL(stp[1]) - softT,
288 r/255.0, g/255.0, b/255.0);
289 UINT_TO_RGBA (clr[3], &r, &g, &b, &a);
290 cairo_pattern_add_color_stop_rgb (pat, YVAL(stp[1]) + softB,
291 r/255.0, g/255.0, b/255.0);
292
293 // -40dB
294 UINT_TO_RGBA (clr[2], &r, &g, &b, &a);
295 cairo_pattern_add_color_stop_rgb (pat, YVAL(stp[0]) - softT,
296 r/255.0, g/255.0, b/255.0);
297 UINT_TO_RGBA (clr[1], &r, &g, &b, &a);
298 cairo_pattern_add_color_stop_rgb (pat, YVAL(stp[0]) + softB,
299 r/255.0, g/255.0, b/255.0);
300
301 // -inf
302 UINT_TO_RGBA (clr[0], &r, &g, &b, &a);
303 cairo_pattern_add_color_stop_rgb (pat, BOF - 4 * onep - softT,
304 r/255.0, g/255.0, b/255.0);
305
306 //Bottom
307 cairo_pattern_add_color_stop_rgb (pat, BOF, .1 , .1, .1);
308 cairo_pattern_add_color_stop_rgb (pat, BOF + onep, .0 , .0, .0);
309 cairo_pattern_add_color_stop_rgb (pat, 1.0, .0 , .0, .0);
310
311 if (!getenv("NO_METER_SHADE") || strlen(getenv("NO_METER_SHADE")) == 0) {
312 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
313 cairo_pattern_add_color_stop_rgba (shade_pattern, .0, 0.0, 0.0, 0.0, 0.90);
314 cairo_pattern_add_color_stop_rgba (shade_pattern, (GM_LEFT-1.0) / GM_WIDTH, 0.0, 0.0, 0.0, 0.55);
315 cairo_pattern_add_color_stop_rgba (shade_pattern, (GM_LEFT + GM_GIRTH * .35) / GM_WIDTH, 1.0, 1.0, 1.0, 0.12);
316 cairo_pattern_add_color_stop_rgba (shade_pattern, (GM_LEFT + GM_GIRTH * .53) / GM_WIDTH, 0.0, 0.0, 0.0, 0.05);
317 cairo_pattern_add_color_stop_rgba (shade_pattern, (GM_LEFT+1.0+GM_GIRTH) / GM_WIDTH, 0.0, 0.0, 0.0, 0.55);
318 cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 0.0, 0.0, 0.0, 0.90);
319
320 cairo_surface_t* surface;
321 cairo_t* tc = 0;
322 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
323 tc = cairo_create (surface);
324 cairo_set_source (tc, pat);
325 cairo_rectangle (tc, 0, 0, width, height);
326 cairo_fill (tc);
327 cairo_pattern_destroy (pat);
328
329 cairo_set_source (tc, shade_pattern);
330 cairo_rectangle (tc, 0, 0, width, height);
331 cairo_fill (tc);
332 cairo_pattern_destroy (shade_pattern);
333
334 // LED stripes
335 cairo_save (tc);
336 cairo_set_line_width(tc, 1.0);
337 cairo_set_source_rgba(tc, .0, .0, .0, 0.6);
338 for (float y=0.5; y < height; y+= 2.0) {
339 cairo_move_to(tc, 0, y);
340 cairo_line_to(tc, width, y);
341 cairo_stroke (tc);
342 }
343 cairo_restore (tc);
344
345 pat = cairo_pattern_create_for_surface (surface);
346 cairo_destroy (tc);
347 cairo_surface_destroy (surface);
348 }
349
350 ui->mpat= pat;
351 }
352
write_text(cairo_t * cr,const char * txt,PangoFontDescription * font,const float x,const float y,const float ang,const int align,const float * const col)353 static void write_text(
354 cairo_t* cr,
355 const char *txt,
356 PangoFontDescription *font,
357 const float x, const float y,
358 const float ang, const int align,
359 const float * const col) {
360 write_text_full(cr, txt, font, x, y, ang, align, col);
361 }
362
363
364 #define FONT_LBL ui->font[FONT_S08]
365 #define FONT_MTR ui->font[FONT_S06]
366 #define FONT_VAL ui->font[FONT_M07]
367 #define FONT_SPK ui->font[FONT_M08]
368
369 #define INIT_ANN_BG(VAR, WIDTH, HEIGHT) \
370 if (VAR) cairo_surface_destroy(VAR); \
371 VAR = cairo_image_surface_create (CAIRO_FORMAT_RGB24, WIDTH, HEIGHT); \
372 cr = cairo_create (VAR);
373
374 #define INIT_BLACK_BG(VAR, WIDTH, HEIGHT) \
375 INIT_ANN_BG(VAR, WIDTH, HEIGHT) \
376 CairoSetSouerceRGBA(c_blk); \
377 cairo_rectangle (cr, 0, 0, WIDTH, WIDTH); \
378 cairo_fill (cr);
379
alloc_annotations(SAUI * ui)380 static void alloc_annotations(SAUI* ui) {
381 cairo_t* cr;
382 if (ui->display_freq) {
383 /* frequecy table */
384 for (uint32_t i = 0; i < ui->num_meters; ++i) {
385 INIT_BLACK_BG(ui->an[i], 24, FQ_WIDTH)
386 write_text(cr, freq_table[i], FONT_LBL, 0, 0, -M_PI/2, 7, c_g90);
387 cairo_destroy (cr);
388 }
389 }
390
391 if (ui->dial) {
392 return; // XXX TODO *dynamic* widget scaling
393 cairo_surface_destroy(ui->dial);
394 }
395 ui->dial = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 2 * (GED_WIDTH), 2 * (GED_HEIGHT+10));
396 cr = cairo_create (ui->dial);
397 cairo_scale (cr, 2.0, 2.0);
398 CairoSetSouerceRGBA(c_trs);
399 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
400 cairo_rectangle (cr, 0, 0, GED_WIDTH, GED_HEIGHT+10);
401 cairo_fill (cr);
402 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
403
404 float xlp, ylp;
405 #define RESPLABLEL(V) \
406 { \
407 float ang = (-.75 * M_PI) + (1.5 * M_PI) * (RESPSCALE(V) - RESPSCALE(.05)) / (RESPSCALE(8.) - RESPSCALE(.05)); \
408 xlp = GED_CX + .5 + sinf (ang) * (GED_RADIUS + 3.0); \
409 ylp = GED_CY + 10.5 - cosf (ang) * (GED_RADIUS + 3.0); \
410 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); \
411 CairoSetSouerceRGBA(ui->c_txt); \
412 cairo_set_line_width(cr, 1.5); \
413 cairo_move_to(cr, rint(xlp)-.5, rint(ylp)-.5); \
414 cairo_close_path(cr); \
415 cairo_stroke(cr); \
416 xlp = GED_CX + .5 + sinf (ang) * (GED_RADIUS + 9.5); \
417 ylp = GED_CY + 10.5 - cosf (ang) * (GED_RADIUS + 9.5); \
418 }
419
420 RESPLABLEL(8.0); write_text(cr, "1/8", FONT_MTR, xlp, ylp, 0, 2, ui->c_txt);
421 RESPLABLEL(4.0); write_text(cr, " 1/4", FONT_MTR, xlp, ylp, 0, 2, ui->c_txt);
422 RESPLABLEL(2.0); write_text(cr, " 1/2", FONT_MTR, xlp, ylp, 0, 2, ui->c_txt);
423 RESPLABLEL(1.0); write_text(cr, "1", FONT_MTR, xlp, ylp, 0, 2, ui->c_txt);
424 RESPLABLEL(1/2.f); write_text(cr, "2", FONT_MTR, xlp, ylp, 0, 2, ui->c_txt);
425 RESPLABLEL(1/4.f); write_text(cr, "4", FONT_MTR, xlp, ylp, 0, 2, ui->c_txt);
426 RESPLABLEL(1/10.f); write_text(cr, "10 ", FONT_MTR, xlp, ylp, 0, 2, ui->c_txt);
427 RESPLABLEL(1/20.f); write_text(cr, "20", FONT_MTR, xlp, ylp, 0, 2, ui->c_txt);
428 cairo_destroy (cr);
429 }
430
realloc_metrics(SAUI * ui)431 static void realloc_metrics(SAUI* ui) {
432 const float dboff = ui->gain;
433 if (!ui->size_changed && rint(ui->cache_ma * 5) == rint(dboff * 5)) {
434 return;
435 }
436 ui->cache_ma = dboff;
437 cairo_t* cr;
438 #define DO_THE_METER(DB, TXT) \
439 if (dboff + DB < 6.0 && dboff + DB >= -60) \
440 write_text(cr, TXT , FONT_MTR, MA_WIDTH - 3, YPOS(deflect(ui, dboff + DB)), 0, 1, c_g90);
441
442 #define DO_THE_METRICS \
443 DO_THE_METER( 25, "+25dB") \
444 DO_THE_METER( 20, "+20dB") \
445 DO_THE_METER( 18, "+18dB") \
446 DO_THE_METER( 15, "+15dB") \
447 DO_THE_METER( 9, "+9dB") \
448 DO_THE_METER( 6, "+6dB") \
449 DO_THE_METER( 3, "+3dB") \
450 DO_THE_METER( 0, " 0dB") \
451 DO_THE_METER( -3, "-3dB") \
452 DO_THE_METER( -9, "-9dB") \
453 DO_THE_METER( -15, "-15dB") \
454 DO_THE_METER( -18, "-18dB") \
455 DO_THE_METER( -20, "-20dB") \
456 DO_THE_METER( -25, "-25dB") \
457 DO_THE_METER( -30, "-30dB") \
458 DO_THE_METER( -40, "-40dB") \
459 DO_THE_METER( -50, "-50dB") \
460 DO_THE_METER( -60, "-60dB") \
461
462 INIT_ANN_BG(ui->ma[0], MA_WIDTH, GM_HEIGHT);
463 CairoSetSouerceRGBA(ui->c_bgr);
464 cairo_rectangle (cr, 0, 0, MA_WIDTH, GM_HEIGHT);
465 cairo_fill (cr);
466 DO_THE_METRICS
467 if (ui->display_freq) {
468 write_text(cr, "dBFS", FONT_MTR, MA_WIDTH - 5, GM_TXT - 5, 0, 1, c_g90);
469 }
470 cairo_destroy (cr);
471
472 INIT_ANN_BG(ui->ma[1], MA_WIDTH, GM_HEIGHT)
473 CairoSetSouerceRGBA(ui->c_bgr);
474 cairo_rectangle (cr, 0, 0, MA_WIDTH, GM_HEIGHT);
475 cairo_fill (cr);
476 DO_THE_METRICS
477 if (ui->display_freq) {
478 write_text(cr, "dBFS", FONT_MTR, MA_WIDTH - 5, GM_TXT - 5, 0, 1, c_g90);
479 } else {
480 write_text(cr, "dBTP", FONT_MTR, MA_WIDTH - 5, GM_TXT - 5 + ceil(GM_PEAK * .5), 0, 1, c_g90);
481 }
482 cairo_destroy (cr);
483 }
484
prepare_metersurface(SAUI * ui)485 static void prepare_metersurface(SAUI* ui) {
486 const float dboff = ui->gain;
487
488 if (!ui->size_changed && rint(ui->cache_sf * 5) == rint(dboff * 5)) {
489 return;
490 }
491 ui->cache_sf = dboff;
492
493 cairo_t* cr;
494 #define ALLOC_SF(VAR) \
495 if (VAR) cairo_surface_destroy(VAR); \
496 VAR = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, GM_WIDTH, GM_HEIGHT);\
497 cr = cairo_create (VAR);\
498 CairoSetSouerceRGBA(ui->c_bgr); \
499 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);\
500 cairo_rectangle (cr, 0, 0, GM_WIDTH, GM_HEIGHT);\
501 cairo_fill (cr);
502
503 #define GAINLINE(DB) \
504 if (dboff + DB < 5.99) { \
505 const float yoff = GM_TOP + GM_SCALE - deflect(ui, dboff + DB); \
506 cairo_move_to(cr, 0, yoff); \
507 cairo_line_to(cr, GM_WIDTH, yoff); \
508 cairo_stroke(cr); \
509 }
510
511 for (uint32_t i = 0; i < ui->num_meters; ++i) {
512 ALLOC_SF(ui->sf[i])
513
514 /* metric background */
515 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
516 cairo_set_line_width(cr, 1.0);
517 CairoSetSouerceRGBA(c_g80);
518 GAINLINE(25);
519 GAINLINE(20);
520 GAINLINE(18);
521 GAINLINE(15);
522 GAINLINE(9);
523 GAINLINE(6);
524 GAINLINE(3);
525 GAINLINE(0);
526 GAINLINE(-3);
527 GAINLINE(-9);
528 GAINLINE(-15);
529 GAINLINE(-18);
530 GAINLINE(-20);
531 GAINLINE(-25);
532 GAINLINE(-30);
533 GAINLINE(-40);
534 GAINLINE(-50);
535 GAINLINE(-60);
536 cairo_destroy(cr);
537
538 render_meter(ui, i, GM_SCALE, 2, 0, 0);
539 ui->val_vis[i] = 2;
540 ui->peak_vis[i] = 0;
541 }
542 }
543
render_meter(SAUI * ui,int i,int v_old,int v_new,int m_old,int m_new)544 static void render_meter(SAUI* ui, int i, int v_old, int v_new, int m_old, int m_new) {
545 cairo_t* cr = cairo_create (ui->sf[i]);
546 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
547
548 CairoSetSouerceRGBA(c_blk);
549 rounded_rectangle (cr, GM_LEFT-.5, GM_TOP, GM_GIRTH+1, GM_SCALE, 6);
550 cairo_fill_preserve(cr);
551 cairo_clip(cr);
552
553 cairo_set_source(cr, ui->mpat);
554 cairo_rectangle (cr, GM_LEFT, GM_TOP + GM_SCALE - v_new - 1, GM_GIRTH, v_new + 1);
555 cairo_fill(cr);
556
557 if (ui->show_peaks) {
558 /* peak hold */
559 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
560 cairo_rectangle (cr, GM_LEFT, GM_TOP + GM_SCALE - m_new - 0.5, GM_GIRTH, 3);
561 cairo_fill_preserve (cr);
562 CairoSetSouerceRGBA(c_hlt);
563 cairo_fill(cr);
564 }
565
566 /* border */
567 cairo_reset_clip(cr);
568 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
569
570 cairo_set_line_width(cr, .75);
571 CairoSetSouerceRGBA(c_g60);
572 rounded_rectangle (cr, GM_LEFT, GM_TOP, GM_GIRTH, GM_SCALE, 6);
573 cairo_stroke(cr);
574
575 cairo_destroy(cr);
576 }
577
578 /******************************************************************************
579 * main drawing
580 */
581
format_db(char * buf,const float val)582 static void format_db(char *buf, const float val) {
583 if (val > 99) {
584 sprintf(buf, "++++");
585 }
586 else if (val > -10) {
587 sprintf(buf, "%+.1f", val);
588 }
589 else if (val > -99.9) {
590 sprintf(buf, "%.0f ", val);
591 }
592 else {
593 sprintf(buf, " -\u221E ");
594 }
595 }
596
format_val(char * buf,const float val)597 static void format_val(char *buf, const float val) {
598 if (val > 99) {
599 sprintf(buf, "+++++");
600 }
601 else if (val > -99.9) {
602 sprintf(buf, "%+5.1f", val);
603 }
604 else {
605 sprintf(buf, " -\u221E ");
606 }
607 }
608
609
expose_event(RobWidget * handle,cairo_t * cr,cairo_rectangle_t * ev)610 static bool expose_event(RobWidget* handle, cairo_t* cr, cairo_rectangle_t *ev) {
611 SAUI* ui = (SAUI*)GET_HANDLE(handle);
612
613 if (ui->size_changed) {
614 reinitialize_font_cache(ui);
615 alloc_annotations(ui);
616 //robtk_dial_set_surface(ui->spn_speed, ui->dial); // XXX
617 create_meter_pattern(ui);
618 }
619 if (ui->metrics_changed || ui->size_changed) {
620 realloc_metrics(ui);
621 prepare_metersurface(ui);
622 ui->metrics_changed = false;
623 ui->size_changed = false;
624 }
625
626 cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height);
627 cairo_clip (cr);
628
629 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
630
631 /* metric areas */
632 if (rect_intersect_a(ev, 0, 0, MA_WIDTH, GM_HEIGHT)) {
633 cairo_set_source_surface(cr, ui->ma[0], 0, 0);
634 cairo_paint (cr);
635 }
636 if (rect_intersect_a(ev, MA_WIDTH + GM_WIDTH * ui->num_meters, 0, MA_WIDTH, GM_HEIGHT)) {
637 cairo_set_source_surface(cr, ui->ma[1], MA_WIDTH + GM_WIDTH * ui->num_meters, 0);
638 cairo_paint (cr);
639 }
640
641 for (uint32_t i = 0; i < ui->num_meters ; ++i) {
642 if (!rect_intersect_a(ev, MA_WIDTH + GM_WIDTH * i, 0, GM_WIDTH, GM_HEIGHT)) continue;
643
644 const int v_old = ui->val_vis[i];
645 const int v_new = ui->val_def[i];
646 const int m_old = ui->peak_vis[i];
647 const int m_new = ui->peak_def[i];
648
649 if (v_old != v_new || m_old != m_new || ui->show_peaks_changed) {
650 ui->val_vis[i] = v_new;
651 ui->peak_vis[i] = m_new;
652 render_meter(ui, i, v_old, v_new, m_old, m_new);
653 }
654 cairo_set_source_surface(cr, ui->sf[i], MA_WIDTH + GM_WIDTH * i, 0);
655 cairo_paint (cr);
656 }
657
658 if (ev->width >= ui->width && ev->height >= ui->height) {
659 /* full expose/redraw */
660 ui->show_peaks_changed = false;
661 }
662
663 /* numerical peak and value */
664 if (!ui->display_freq) {
665 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
666 for (uint32_t i = 0; i < ui->num_meters ; ++i) {
667 char buf[8];
668 if (rect_intersect_a(ev, MA_WIDTH + GM_WIDTH * i + 2, 5, GM_WIDTH-4, GM_PEAK)) {
669 cairo_save(cr);
670 rounded_rectangle (cr, MA_WIDTH + GM_WIDTH * i + 2, 5, GM_WIDTH-4, GM_PEAK, 4);
671 if (ui->peak_val[i] >= -1.0) {
672 CairoSetSouerceRGBA(c_ptr);
673 } else {
674 CairoSetSouerceRGBA(c_blk);
675 }
676 cairo_fill(cr);
677 rounded_rectangle (cr, MA_WIDTH + GM_WIDTH * i + 1.5, 5.5, GM_WIDTH-3, GM_PEAK - 1, 4);
678 cairo_set_line_width(cr, 1);
679 CairoSetSouerceRGBA(c_g60);
680 cairo_stroke_preserve (cr);
681 cairo_clip (cr);
682 format_db(buf, ui->peak_val[i]);
683
684 write_text(cr, buf, FONT_VAL, MA_WIDTH + GM_WIDTH * (i + 1) - 5, 5 + ceil(GM_PEAK * .5), 0, 1, c_g90);
685 cairo_restore(cr);
686 }
687
688 if (rect_intersect_a(ev, MA_WIDTH + GM_WIDTH * i + 2, GM_TXT - 5, GM_WIDTH-4, GM_PEAK)) {
689 cairo_save(cr);
690 rounded_rectangle (cr, MA_WIDTH + GM_WIDTH * i + 2, GM_TXT - 5, GM_WIDTH-4, GM_PEAK, 4);
691 CairoSetSouerceRGBA(ui->c_bgr);
692 cairo_fill(cr);
693 rounded_rectangle (cr, MA_WIDTH + GM_WIDTH * i + 1.5, GM_TXT - 4.5, GM_WIDTH-3, GM_PEAK - 1, 4);
694 cairo_set_line_width(cr, 1.0);
695 CairoSetSouerceRGBA(c_g60);
696 cairo_stroke_preserve (cr);
697 cairo_clip (cr);
698 format_db(buf, ui->val[i]);
699 write_text(cr, buf, FONT_VAL, MA_WIDTH + GM_WIDTH * (i + 1) - 5, GM_TXT - 5 + ceil(GM_PEAK * .5), 0, 1, c_g90);
700 cairo_restore(cr);
701 }
702 }
703 }
704
705 /* labels */
706 if (ui->display_freq) {
707 cairo_set_operator (cr, CAIRO_OPERATOR_SCREEN);
708 for (uint32_t i = 0; i < ui->num_meters ; ++i) {
709 if (!rect_intersect_a(ev, MA_WIDTH + GM_WIDTH * i, GM_TXT, 24, 64)) continue;
710 cairo_set_source_surface(cr, ui->an[i], MA_WIDTH + GM_WIDTH * i + rintf(.5 * (GM_WIDTH - 13)), GM_TXT);
711 cairo_paint (cr);
712 }
713 }
714
715 /* highlight */
716 if (ui->highlight >= 0 && ui->highlight < (int) ui->num_meters &&
717 rect_intersect_a(ev, MA_WIDTH + GM_WIDTH * ui->highlight + GM_WIDTH/2 - AN_WIDTH, GM_TXT -4.5, 2 * AN_WIDTH, AN_HEIGHT)) {
718 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
719 const int i = ui->highlight;
720 char buf[32], bufv[8], bufp[8];
721 format_val(bufv, ui->val[i]);
722 format_val(bufp, ui->peak_val[i]);
723 sprintf(buf, "%s\nc:%s\np:%s"
724 , freq_table[i], bufv, bufp);
725 cairo_save(cr);
726 cairo_set_line_width(cr, 0.75);
727 CairoSetSouerceRGBA(c_g90);
728 cairo_move_to(cr, MA_WIDTH + GM_WIDTH * i + GM_LEFT + GM_GIRTH/2, GM_TXT - 8.5);
729 cairo_line_to(cr, MA_WIDTH + GM_WIDTH * i + GM_LEFT + GM_GIRTH/2, GM_TXT - 4.5);
730 cairo_stroke(cr);
731 rounded_rectangle (cr, MA_WIDTH + GM_WIDTH * i + GM_WIDTH/2 - AN_WIDTH, GM_TXT -4.5, 2 * AN_WIDTH, AN_HEIGHT, 3);
732 CairoSetSouerceRGBA(c_xfb);
733 cairo_fill_preserve (cr);
734 CairoSetSouerceRGBA(c_g60);
735 cairo_stroke_preserve (cr);
736 cairo_clip (cr);
737 write_text(cr, buf, FONT_SPK, MA_WIDTH + GM_WIDTH * i + GM_WIDTH/2, GM_TXT + 18 * ui->scale, 0, 2, c_g90);
738 cairo_restore(cr);
739 }
740
741 return TRUE;
742 }
743
744 /******************************************************************************
745 * UI callbacks
746 */
747
cb_reset_peak(RobWidget * handle,RobTkBtnEvent * event)748 static RobWidget* cb_reset_peak (RobWidget* handle, RobTkBtnEvent *event) {
749 SAUI* ui = (SAUI*)GET_HANDLE(handle);
750 ui->reset_toggle = !ui->reset_toggle;
751 /* reset peak-hold in backend */
752 float temp = ui->reset_toggle ? 1.0 : 2.0;
753 ui->write(ui->controller, ui->display_freq? 61 : 0,
754 sizeof(float), 0, (const void*) &temp);
755
756 for (uint32_t i=0; i < ui->num_meters ; ++i) {
757 ui->peak_val[i] = -100;
758 ui->peak_def[i] = deflect(ui, -100);
759 }
760 queue_draw(ui->m0);
761 return NULL;
762 }
763
set_gain(RobWidget * w,void * handle)764 static bool set_gain(RobWidget* w, void* handle) {
765 SAUI* ui = (SAUI*)handle;
766 float oldgain = ui->gain;
767 ui->gain = robtk_scale_get_value(ui->fader);
768 #if 1
769 if (ui->gain < -12) ui->gain = -12;
770 if (ui->gain >= 32.0) ui->gain = 32.0;
771 #endif
772 if (oldgain == ui->gain) return TRUE;
773 if (!ui->disable_signals) {
774 ui->write(ui->controller, 62, sizeof(float), 0, (const void*) &ui->gain);
775 }
776 if (ui->display_freq) { // should actually always be true here
777 #if 0
778 for (uint32_t pidx=0; pidx < ui->num_meters ; ++pidx) {
779 invalidate_meter(ui, pidx, ui->val[pidx], ui->peak_val[pidx]);
780 }
781 #else
782 ui->initialize = 1;
783 float temp = -3;
784 ui->write(ui->controller, ui->display_freq? 61 : 0,
785 sizeof(float), 0, (const void*) &temp);
786 #endif
787 }
788 ui->metrics_changed = true;
789 queue_draw(ui->m0);
790 return TRUE;
791 }
792
set_speed(RobWidget * w,void * handle)793 static bool set_speed(RobWidget* w, void* handle) {
794 SAUI* ui = (SAUI*)handle;
795 if (!ui->disable_signals) {
796 float val = INV_RESPSCALE(robtk_dial_get_value(ui->spn_speed));
797 //printf("set_speed %f -> %f\n", robtk_dial_get_value(ui->spn_speed), val);
798 ui->write(ui->controller, 60, sizeof(float), 0, (const void*) &val);
799 }
800 return TRUE;
801 }
802
set_peakdisplay(RobWidget * w,void * handle)803 static bool set_peakdisplay(RobWidget* w, void* handle) {
804 SAUI* ui = (SAUI*)handle;
805 bool show_peaks = robtk_cbtn_get_active(ui->btn_peaks);
806 ui->misc_state &=~1;
807 if (show_peaks) ui->misc_state |=~1;
808 ui->show_peaks = show_peaks;
809 ui->show_peaks_changed = true;
810 if (!ui->disable_signals) {
811 float misc_state = ui->misc_state;
812 ui->write(ui->controller, 63, sizeof(float), 0, (const void*) &misc_state);
813 }
814 queue_draw(ui->m0);
815 return TRUE;
816 }
817
mousemove(RobWidget * handle,RobTkBtnEvent * event)818 static RobWidget* mousemove(RobWidget* handle, RobTkBtnEvent *event) {
819 SAUI* ui = (SAUI*)GET_HANDLE(handle);
820 if (event->y < GM_TOP || event->y > (GM_TOP + GM_SCALE)) {
821 if (ui->highlight != -1) { queue_draw(ui->m0); }
822 ui->highlight = -1;
823 return NULL;
824 }
825 const int x = event->x - MA_WIDTH;
826 const int remain = x % ((int) GM_WIDTH);
827 if (remain < GM_LEFT || remain > GM_LEFT + GM_GIRTH) {
828 if (ui->highlight != -1) { queue_draw(ui->m0); }
829 ui->highlight = -1;
830 return NULL;
831 }
832
833 const uint32_t mtr = x / ((int) GM_WIDTH);
834 if (mtr < ui->num_meters) {
835 if (ui->highlight != (int) mtr) { queue_draw(ui->m0); }
836 ui->highlight = mtr;
837 } else {
838 if (ui->highlight != -1) { queue_draw(ui->m0); }
839 ui->highlight = -1;
840 }
841 return handle;
842 }
843
844 /******************************************************************************
845 * widget hackery
846 */
847
848 static void
size_request(RobWidget * handle,int * w,int * h)849 size_request(RobWidget* handle, int *w, int *h) {
850 SAUI* ui = (SAUI*)GET_HANDLE(handle);
851 *w = ui->min_width;
852 *h = GM_MINH;
853 }
854
855 static void
top_size_allocate(RobWidget * rw,int w,int h)856 top_size_allocate(RobWidget* rw, int w, int h) {
857 assert (rw->childcount == 3);
858 SAUI* ui = (SAUI*)GET_HANDLE(rw->children[0]->children[0]);
859 GLrobtkLV2UI * const self = (GLrobtkLV2UI*) robwidget_get_toplevel_handle(rw);
860
861 if (ui->_min_w == 0 && ui->_min_h == 0) {
862 if (rw->widget_scale == 1.0) {
863 ui->_min_w = rw->area.width;
864 ui->_min_h = rw->area.height;
865 } else {
866 rhbox_size_allocate (rw, w, h);
867 return;
868 }
869 }
870 assert (ui->_min_w > 1 && ui->_min_h > 1);
871
872 const float scale_x = w / ui->_min_w;
873 const float scale_y = h / ui->_min_h;
874 rw->widget_scale = MAX(1.0, MIN(2.0, floor (MIN(scale_y, scale_x) * 10) / 10));
875
876 if (self->queue_widget_scale != rw->widget_scale) {
877 self->queue_widget_scale = rw->widget_scale;
878 puglPostResize(self->view);
879 queue_draw (rw);
880 }
881 rhbox_size_allocate (rw, w, h);
882 }
883
884 static void
size_allocate(RobWidget * handle,int w,int h)885 size_allocate(RobWidget* handle, int w, int h) {
886 SAUI* ui = (SAUI*)GET_HANDLE(handle);
887 ui->height = floor(h/2) * 2;
888 ui->width = w;
889 ui->size_changed = true;
890
891 float scale_y = ui->height / GM_MINH;
892 float scale_x = (float) ui->width / ui->min_width;
893 ui->scale = MAX(1.0, MIN(2.5, MIN(scale_y, scale_x)));
894
895 if (ui->display_freq) {
896 ui->gm_width = MIN(40, floor ((w - 2.0 * MA_WIDTH) / ui->num_meters));
897 ui->gm_girth = rintf (ui->gm_width * .75);
898 ui->gm_left = .5 + floor(.5 * (ui->gm_width - ui->gm_girth));
899 ui->cur_width = 2.0 * MA_WIDTH + ui->num_meters * GM_WIDTH;
900 } else {
901 ui->gm_width = floor ((w - 2.0 * MA_WIDTH) / ui->num_meters);
902 if (ui->gm_width > 60) ui->gm_width = 60;
903 ui->gm_girth = rintf (ui->gm_width * .42);
904 ui->gm_left = .5 + floor(.5 * (ui->gm_width - ui->gm_girth));
905 ui->cur_width = 2.0 * MA_WIDTH + ui->num_meters * GM_WIDTH;
906 }
907 robwidget_set_size(handle, MIN(ui->width, ui->cur_width), h);
908 queue_draw(ui->m0);
909 }
910
toplevel(SAUI * ui,void * const top)911 static RobWidget * toplevel(SAUI* ui, void * const top)
912 {
913 /* main widget: layout */
914 ui->rw = rob_hbox_new(FALSE, 2);
915 robwidget_make_toplevel(ui->rw, top);
916 if (ui->display_freq) {
917 robwidget_toplevel_enable_scaling (ui->rw);
918 }
919
920 /* DPM main drawing area */
921 ui->m0 = robwidget_new(ui);
922 ROBWIDGET_SETNAME(ui->m0, "dpm (m0)");
923
924 robwidget_set_expose_event(ui->m0, expose_event);
925 robwidget_set_size_request(ui->m0, size_request);
926 robwidget_set_size_allocate(ui->m0, size_allocate);
927 robwidget_set_mousedown(ui->m0, cb_reset_peak);
928 if (ui->display_freq) {
929 robwidget_set_mousemove(ui->m0, mousemove);
930 }
931 ui->sep_h0 = robtk_sep_new(FALSE);
932
933 /* vbox on the right */
934 ui->c_box = rob_vbox_new(FALSE, 2);
935
936 ui->fader = robtk_scale_new(-12, 32.0, .05, FALSE);
937 ui->lbl_speed = robtk_lbl_new("Response [s]");
938 ui->spn_speed = robtk_dial_new_with_size(RESPSCALE(.05), RESPSCALE(8), .1, GED_WIDTH, GED_HEIGHT+10, GED_CX, GED_CY+10, GED_RADIUS);
939 ui->btn_peaks = robtk_cbtn_new("Peak Hold", GBT_LED_LEFT, true);
940 robtk_cbtn_set_active(ui->btn_peaks, true);
941 robtk_dial_set_default(ui->spn_speed, RESPSCALE(1.0f));
942 robtk_dial_set_scaled_surface_scale (ui->spn_speed, ui->dial, 2.0);
943
944 robtk_scale_set_default(ui->fader, 0);
945 robtk_scale_set_value(ui->fader, 0);
946 robtk_scale_add_mark(ui->fader, -9, "-9dB");
947 robtk_scale_add_mark(ui->fader, -6, "-6dB");
948 robtk_scale_add_mark(ui->fader, -3, "-3dB");
949 robtk_scale_add_mark(ui->fader, 0, "0dB");
950 robtk_scale_add_mark(ui->fader, 3, "+3dB");
951 robtk_scale_add_mark(ui->fader, 6, "+6dB");
952 robtk_scale_add_mark(ui->fader, 9, "+9dB");
953 robtk_scale_add_mark(ui->fader, 10, "");
954 robtk_scale_add_mark(ui->fader, 12, "+12dB");
955 robtk_scale_add_mark(ui->fader, 15, "+15dB");
956 robtk_scale_add_mark(ui->fader, 18, "+18dB");
957 robtk_scale_add_mark(ui->fader, 20, "+20dB");
958 robtk_scale_add_mark(ui->fader, 25, "+25dB");
959 robtk_scale_add_mark(ui->fader, 30, "+30dB");
960
961 /* layout */
962
963 ui->m_box = rob_vbox_new(FALSE, 0);
964 rob_vbox_child_pack(ui->m_box, ui->m0, TRUE, TRUE);
965 rob_hbox_child_pack(ui->rw, ui->m_box, TRUE, TRUE);
966
967 if (ui->display_freq) {
968 rob_hbox_child_pack(ui->rw, robtk_sep_widget(ui->sep_h0), FALSE, TRUE);
969 rob_hbox_child_pack(ui->rw, ui->c_box, FALSE, TRUE);
970
971 rob_vbox_child_pack(ui->c_box, robtk_scale_widget(ui->fader), TRUE, TRUE);
972 #if 1 // display speed, value-LPF config
973 rob_vbox_child_pack(ui->c_box, robtk_lbl_widget(ui->lbl_speed), FALSE, FALSE);
974 rob_vbox_child_pack(ui->c_box, robtk_dial_widget(ui->spn_speed), FALSE, FALSE);
975 #endif
976 rob_vbox_child_pack(ui->c_box, robtk_cbtn_widget(ui->btn_peaks), FALSE, FALSE);
977 }
978
979 /* callbacks */
980 robtk_scale_set_callback(ui->fader, set_gain, ui);
981 robtk_dial_set_callback(ui->spn_speed, set_speed, ui);
982 robtk_cbtn_set_callback(ui->btn_peaks, set_peakdisplay, ui);
983
984 /* change _after_ packing, (packing checks allocate fn ptr) */
985 if (ui->display_freq) {
986 ui->rw->size_allocate = top_size_allocate;
987 }
988
989 return ui->rw;
990 }
991
992 /******************************************************************************
993 * LV2 callbacks
994 */
995
ui_enable(LV2UI_Handle handle)996 static void ui_enable(LV2UI_Handle handle) { }
ui_disable(LV2UI_Handle handle)997 static void ui_disable(LV2UI_Handle handle) { }
998
999 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)1000 instantiate(
1001 void* const ui_toplevel,
1002 const LV2UI_Descriptor* descriptor,
1003 const char* plugin_uri,
1004 const char* bundle_path,
1005 LV2UI_Write_Function write_function,
1006 LV2UI_Controller controller,
1007 RobWidget** widget,
1008 const LV2_Feature* const* features)
1009 {
1010 SAUI* ui = (SAUI*) calloc(1,sizeof(SAUI));
1011 *widget = NULL;
1012
1013 if (!strcmp(plugin_uri, MTR_URI "spectr30mono")) { ui->num_meters = 30; ui->display_freq = true; }
1014 else if (!strcmp(plugin_uri, MTR_URI "spectr30stereo")) { ui->num_meters = 30; ui->display_freq = true; }
1015 else if (!strcmp(plugin_uri, MTR_URI "dBTPmono")) { ui->num_meters = 1; ui->display_freq = false; }
1016 else if (!strcmp(plugin_uri, MTR_URI "dBTPstereo")) { ui->num_meters = 2; ui->display_freq = false; }
1017 else {
1018 free(ui);
1019 return NULL;
1020 }
1021 ui->write = write_function;
1022 ui->controller = controller;
1023 ui->scale = 1.0;
1024
1025 get_color_from_theme(0, ui->c_txt);
1026 get_color_from_theme(1, ui->c_bgr);
1027
1028 ui->gain = 0;
1029 ui->cache_sf = -100;
1030 ui->cache_ma = -100;
1031 ui->highlight = -1;
1032 ui->metrics_changed = true;
1033 ui->size_changed = true;
1034
1035 ui->_min_w = 0;
1036 ui->_min_h = 0;
1037
1038 ui->show_peaks_changed = false;
1039 ui->show_peaks = true;
1040 ui->misc_state = 1;
1041
1042 for (uint32_t i=0; i < ui->num_meters ; ++i) {
1043 ui->val[i] = -100.0;
1044 ui->val_def[i] = deflect(ui, -100);
1045 ui->peak_val[i] = -100.0;
1046 ui->peak_def[i] = deflect(ui, -100);
1047 }
1048 ui->disable_signals = false;
1049
1050 if (ui->display_freq) {
1051 ui->gm_width = 13.f;
1052 ui->gm_girth = 10.f;
1053 ui->gm_left = 1.5f;
1054 } else {
1055 ui->gm_width = 28.f;
1056 ui->gm_girth = 12.f;
1057 ui->gm_left = 8.5f;
1058 }
1059 ui->min_width = 2.0 * MA_WIDTH + ui->num_meters * GM_WIDTH;
1060 ui->width = ui->min_width;
1061 ui->height = GM_MINH;
1062
1063 reinitialize_font_cache(ui);
1064 alloc_annotations(ui);
1065
1066 *widget = toplevel(ui, ui_toplevel);
1067
1068 ui->initialize = 0;
1069 ui->reset_toggle = false;
1070 return ui;
1071 }
1072
1073 static enum LVGLResize
plugin_scale_mode(LV2UI_Handle handle)1074 plugin_scale_mode(LV2UI_Handle handle)
1075 {
1076 return LVGL_LAYOUT_TO_FIT;
1077 }
1078
1079 static void
cleanup(LV2UI_Handle handle)1080 cleanup(LV2UI_Handle handle)
1081 {
1082 SAUI* ui = (SAUI*)handle;
1083 for (uint32_t i=0; i < ui->num_meters ; ++i) {
1084 cairo_surface_destroy(ui->sf[i]);
1085 cairo_surface_destroy(ui->an[i]);
1086 }
1087 for (int i=0; i < 4; ++i) {
1088 pango_font_description_free(ui->font[i]);
1089 }
1090 cairo_pattern_destroy(ui->mpat);
1091 cairo_surface_destroy(ui->ma[0]);
1092 cairo_surface_destroy(ui->ma[1]);
1093 cairo_surface_destroy(ui->dial);
1094
1095 robtk_scale_destroy(ui->fader);
1096 robtk_lbl_destroy(ui->lbl_speed);
1097 robtk_dial_destroy(ui->spn_speed);
1098 robtk_cbtn_destroy(ui->btn_peaks);
1099 robtk_sep_destroy(ui->sep_h0);
1100 rob_box_destroy(ui->c_box);
1101
1102 robwidget_destroy(ui->m0);
1103 rob_box_destroy(ui->m_box);
1104 rob_box_destroy(ui->rw);
1105
1106 free(ui);
1107 }
1108
1109 static const void*
extension_data(const char * uri)1110 extension_data(const char* uri)
1111 {
1112 return NULL;
1113 }
1114
1115 /******************************************************************************
1116 * backend communication
1117 */
invalidate_meter(SAUI * ui,int mtr,float val,float peak)1118 static void invalidate_meter(SAUI* ui, int mtr, float val, float peak) {
1119 const int v_old = ui->val_def[mtr];
1120 const int v_new = deflect(ui, val + ui->gain);
1121
1122 const int m_old = ui->peak_def[mtr];
1123 const int m_new = ceilf(deflect(ui, peak + ui->gain) / 2.0) * 2.0;
1124 int t, h;
1125
1126 #define INVALIDATE_RECT(XX,YY,WW,HH) \
1127 queue_tiny_area(ui->m0, XX, YY, WW, HH);
1128
1129 if (rintf(ui->val[mtr] * 10.0f) != rintf(val * 10.0f) && !ui->display_freq) {
1130 INVALIDATE_RECT(mtr * GM_WIDTH + MA_WIDTH, GM_TXT - 5, GM_WIDTH, GM_PEAK + 1);
1131 }
1132
1133 if (ui->highlight == mtr && ui->display_freq &&
1134 (rintf(ui->val[mtr] * 10.0f) != rintf(val * 10.0f) || rintf(m_old * 10.0f) != rintf(m_new * 10.0f))) {
1135 queue_tiny_area(ui->m0, mtr * GM_WIDTH + MA_WIDTH + GM_WIDTH/2 - AN_WIDTH -.5, GM_TXT - 8, 1 + 2 * AN_WIDTH, FQ_ANN);
1136 }
1137
1138 if (rintf(ui->peak_val[mtr] * 10.0f) != rintf(peak * 10.0f) && !ui->display_freq) {
1139 INVALIDATE_RECT(mtr * GM_WIDTH + MA_WIDTH, 5, GM_WIDTH, GM_PEAK + 1);
1140 }
1141
1142 ui->val[mtr] = val;
1143 ui->val_def[mtr] = v_new;
1144 ui->peak_val[mtr] = peak;
1145 ui->peak_def[mtr] = m_new;
1146
1147 if (v_old != v_new) {
1148 if (v_old > v_new) {
1149 t = v_old;
1150 h = v_old - v_new;
1151 } else {
1152 t = v_new;
1153 h = v_new - v_old;
1154 }
1155
1156 INVALIDATE_RECT(
1157 mtr * GM_WIDTH + MA_WIDTH + GM_LEFT - 1,
1158 GM_TOP + GM_SCALE - t - 1,
1159 GM_GIRTH + 2, h+3);
1160 }
1161
1162 if (m_old != m_new && ui->show_peaks) {
1163 if (m_old > m_new) {
1164 t = m_old;
1165 h = m_old - m_new;
1166 } else {
1167 t = m_new;
1168 h = m_new - m_old;
1169 }
1170
1171 INVALIDATE_RECT(
1172 mtr * GM_WIDTH + MA_WIDTH + GM_LEFT - 1,
1173 GM_TOP + GM_SCALE - t - 1,
1174 GM_GIRTH + 2, h+4);
1175 }
1176 }
1177
1178 /******************************************************************************
1179 * handle data from backend
1180 */
1181
handle_spectrum_connections(SAUI * ui,uint32_t port_index,float v)1182 static void handle_spectrum_connections(SAUI* ui, uint32_t port_index, float v) {
1183
1184 if (port_index == 62) {
1185 if (v >= -12 && v <= 32.0) {
1186 ui->disable_signals = true;
1187 robtk_scale_set_value(ui->fader, v);
1188 ui->disable_signals = false;
1189 }
1190 } else
1191 if (port_index == 63) {
1192 if (v >= 0 && v <= 256.0) {
1193 ui->disable_signals = true;
1194 robtk_cbtn_set_active(ui->btn_peaks, (((int)v)&1) == 1);
1195 ui->disable_signals = false;
1196 }
1197 } else
1198 if (port_index == 60) {
1199 ui->disable_signals = true;
1200 if (v > 0 && v < 15) {
1201 robtk_dial_set_value(ui->spn_speed, RESPSCALE(v));
1202 }
1203 ui->disable_signals = false;
1204 } else
1205 if (v > -500 && port_index < 30) {
1206 int pidx = port_index;
1207 float np = ui->peak_val[pidx];
1208 invalidate_meter(ui, pidx, v, np);
1209 }
1210 if (v > -500 && port_index >= 30 && port_index < 60) {
1211 int pidx = port_index - 30;
1212 float nv = ui->val[pidx];
1213 invalidate_meter(ui, pidx, nv, v);
1214 }
1215 }
1216
handle_meter_connections(SAUI * ui,uint32_t port_index,float v)1217 static void handle_meter_connections(SAUI* ui, uint32_t port_index, float v) {
1218 if (v <= -500) return;
1219 v = v > .00001f ? 20.0 * log10f(v) : -100.0;
1220 if (port_index == 3) {
1221 invalidate_meter(ui, 0, v, ui->peak_val[0]);
1222 }
1223 else if (port_index == 6) {
1224 invalidate_meter(ui, 1, v, ui->peak_val[1]);
1225 }
1226
1227 /* peak data from backend */
1228 if (ui->num_meters == 1) {
1229 if (port_index == 4) {
1230 float np = ui->peak_val[0];
1231 if (v != np) { np = v; }
1232 invalidate_meter(ui, 0, ui->val[0], np);
1233 }
1234 } else if (ui->num_meters == 2) {
1235 if (port_index == 7) {
1236 float np = ui->peak_val[0];
1237 if (v != np) { np = v; }
1238 invalidate_meter(ui, 0, ui->val[0], np);
1239 }
1240 else if (port_index == 8) {
1241 float np = ui->peak_val[1];
1242 if (v != np) { np = v; }
1243 invalidate_meter(ui, 1, ui->val[1], np);
1244 }
1245 }
1246 }
1247
1248 static void
port_event(LV2UI_Handle handle,uint32_t port_index,uint32_t buffer_size,uint32_t format,const void * buffer)1249 port_event(LV2UI_Handle handle,
1250 uint32_t port_index,
1251 uint32_t buffer_size,
1252 uint32_t format,
1253 const void* buffer)
1254 {
1255 SAUI* ui = (SAUI*)handle;
1256 if (format != 0) return;
1257
1258 if (ui->initialize == 0 && port_index == (ui->display_freq? 61 : 0)) {
1259 ui->initialize = 1;
1260 float temp = -3;
1261 ui->write(ui->controller, ui->display_freq? 61 : 0,
1262 sizeof(float), 0, (const void*) &temp);
1263 } else if (ui->initialize == 1 && *(float *)buffer <= -500 && (
1264 (ui->display_freq && port_index >= 30 && port_index < 60)
1265 || (!ui->display_freq && ui->num_meters == 1 && port_index == 4)
1266 || (!ui->display_freq && ui->num_meters == 2 && port_index == 7)
1267 )) {
1268 ui->initialize = 2;
1269 float temp = -4;
1270 ui->write(ui->controller, ui->display_freq? 61 : 0,
1271 sizeof(float), 0, (const void*) &temp);
1272 }
1273
1274 if (ui->display_freq) {
1275 handle_spectrum_connections(ui, port_index, *(float *)buffer);
1276 } else {
1277 handle_meter_connections(ui, port_index, *(float *)buffer);
1278 }
1279
1280 }
1281