New visualization of the compressor state in graph view
[ardour.git] / libs / plugins / a-exp.lv2 / a-exp.c
1 /* a-exp
2  * Copyright (C) 2017 Johannes Mueller <github@johannes-mueller.org>
3  * based on a-comp (C) 2016 Damien Zammit <damien@zamaudio.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) 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
16
17
18 #include <math.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <stdbool.h>
22
23 #ifdef LV2_EXTENDED
24 #include <cairo/cairo.h>
25 #include "ardour/lv2_extensions.h"
26 #endif
27
28 #include "lv2/lv2plug.in/ns/lv2core/lv2.h"
29
30 #define AEXP_URI "urn:ardour:a-exp"
31 #define AEXP_STEREO_URI "urn:ardour:a-exp#stereo"
32
33 #ifndef M_PI
34 #  define M_PI 3.14159265358979323846
35 #endif
36
37 #ifdef COMPILER_MSVC
38 #include <float.h>
39 #define isfinite_local(val) (bool)_finite((double)val)
40 #else
41 #define isfinite_local isfinite
42 #endif
43
44 typedef enum {
45         AEXP_ATTACK = 0,
46         AEXP_RELEASE,
47         AEXP_KNEE,
48         AEXP_RATIO,
49         AEXP_THRESHOLD,
50         AEXP_MAKEUP,
51
52         AEXP_GAINR,
53         AEXP_OUTLEVEL,
54         AEXP_SIDECHAIN,
55         AEXP_ENABLE,
56
57         AEXP_A0,
58         AEXP_A1,
59         AEXP_A2,
60         AEXP_A3,
61         AEXP_A4,
62 } PortIndex;
63
64 typedef struct {
65         float* attack;
66         float* release;
67         float* knee;
68         float* ratio;
69         float* thresdb;
70         float* makeup;
71
72         float* gainr;
73         float* outlevel;
74         float* sidechain;
75         float* enable;
76
77         float* input0;
78         float* input1;
79         float* sc;
80         float* output0;
81         float* output1;
82
83         float srate;
84
85         float makeup_gain;
86         float tau;
87
88 #ifdef LV2_EXTENDED
89         LV2_Inline_Display_Image_Surface surf;
90         bool                     need_expose;
91         cairo_surface_t*         display;
92         LV2_Inline_Display*      queue_draw;
93         uint32_t                 w, h;
94
95         /* ports pointers are only valid during run so we'll
96          * have to cache them for the display, besides
97          * we do want to check for changes
98          */
99         float v_knee;
100         float v_ratio;
101         float v_thresdb;
102         float v_gainr;
103         float v_makeup;
104         float v_lvl_in;
105         float v_lvl_out;
106 #endif
107 } AExp;
108
109 static LV2_Handle
110 instantiate(const LV2_Descriptor* descriptor,
111             double rate,
112             const char* bundle_path,
113             const LV2_Feature* const* features)
114 {
115         AExp* aexp = (AExp*)calloc(1, sizeof(AExp));
116
117         for (int i=0; features[i]; ++i) {
118 #ifdef LV2_EXTENDED
119                 if (!strcmp(features[i]->URI, LV2_INLINEDISPLAY__queue_draw)) {
120                         aexp->queue_draw = (LV2_Inline_Display*) features[i]->data;
121                 }
122 #endif
123         }
124
125         aexp->srate = rate;
126         aexp->tau = (1.0 - exp (-2.f * M_PI * 25.f / aexp->srate));
127 #ifdef LV2_EXTENDED
128         aexp->need_expose = true;
129         aexp->v_lvl_out = -70.f;
130 #endif
131
132         return (LV2_Handle)aexp;
133 }
134
135 static void
136 connect_port(LV2_Handle instance,
137              uint32_t port,
138              void* data)
139 {
140         AExp* aexp = (AExp*)instance;
141
142         switch ((PortIndex)port) {
143                 case AEXP_ATTACK:
144                         aexp->attack = (float*)data;
145                         break;
146                 case AEXP_RELEASE:
147                         aexp->release = (float*)data;
148                         break;
149                 case AEXP_KNEE:
150                         aexp->knee = (float*)data;
151                         break;
152                 case AEXP_RATIO:
153                         aexp->ratio = (float*)data;
154                         break;
155                 case AEXP_THRESHOLD:
156                         aexp->thresdb = (float*)data;
157                         break;
158                 case AEXP_MAKEUP:
159                         aexp->makeup = (float*)data;
160                         break;
161                 case AEXP_GAINR:
162                         aexp->gainr = (float*)data;
163                         break;
164                 case AEXP_OUTLEVEL:
165                         aexp->outlevel = (float*)data;
166                         break;
167                 case AEXP_SIDECHAIN:
168                         aexp->sidechain = (float*)data;
169                         break;
170                 case AEXP_ENABLE:
171                         aexp->enable = (float*)data;
172                         break;
173                 default:
174                         break;
175         }
176 }
177
178 static void
179 connect_mono(LV2_Handle instance,
180              uint32_t port,
181              void* data)
182 {
183         AExp* aexp = (AExp*)instance;
184         connect_port (instance, port, data);
185
186         switch ((PortIndex)port) {
187                 case AEXP_A0:
188                         aexp->input0 = (float*)data;
189                         break;
190                 case AEXP_A1:
191                         aexp->sc = (float*)data;
192                         break;
193                 case AEXP_A2:
194                         aexp->output0 = (float*)data;
195                         break;
196         default:
197                 break;
198         }
199 }
200
201 static void
202 connect_stereo(LV2_Handle instance,
203                uint32_t port,
204                void* data)
205 {
206         AExp* aexp = (AExp*)instance;
207         connect_port (instance, port, data);
208
209         switch ((PortIndex)port) {
210                 case AEXP_A0:
211                         aexp->input0 = (float*)data;
212                         break;
213                 case AEXP_A1:
214                         aexp->input1 = (float*)data;
215                         break;
216                 case AEXP_A2:
217                         aexp->sc = (float*)data;
218                         break;
219                 case AEXP_A3:
220                         aexp->output0 = (float*)data;
221                         break;
222                 case AEXP_A4:
223                         aexp->output1 = (float*)data;
224                         break;
225         default:
226                 break;
227         }
228 }
229
230 // Force already-denormal float value to zero
231 static inline float
232 sanitize_denormal(float value) {
233         if (!isnormal(value)) {
234                 value = 0.f;
235         }
236         return value;
237 }
238
239 static inline float
240 from_dB(float gdb) {
241         return (exp(gdb/20.f*log(10.f)));
242 }
243
244 static inline float
245 to_dB(float g) {
246         return (20.f*log10(g));
247 }
248
249 static void
250 activate(LV2_Handle instance)
251 {
252         AExp* aexp = (AExp*)instance;
253
254         *(aexp->gainr) = 160.0f;
255         *(aexp->outlevel) = -45.0f;
256 }
257
258 static void
259 run_mono(LV2_Handle instance, uint32_t n_samples)
260 {
261         AExp* aexp = (AExp*)instance;
262
263         const float* const input = aexp->input0;
264         const float* const sc = aexp->sc;
265         float* const output = aexp->output0;
266
267         float srate = aexp->srate;
268         float width = (6.f * *(aexp->knee)) + 0.01;
269         float attack_coeff = exp(-1000.f/(*(aexp->attack) * srate));
270         float release_coeff = exp(-1000.f/(*(aexp->release) * srate));
271
272         float max = 0.f;
273         float lgaininp = 0.f;
274         float Lgain = 1.f;
275         float Lxg, Lyg;
276         float current_gainr;
277         float old_gainr = *aexp->gainr;
278
279         int usesidechain = (*(aexp->sidechain) <= 0.f) ? 0 : 1;
280         uint32_t i;
281         float ingain;
282         float in0;
283         float sc0;
284
285         float ratio = *aexp->ratio;
286         float thresdb = *aexp->thresdb;
287         float makeup = *aexp->makeup;
288         float makeup_target = from_dB(makeup);
289         float makeup_gain = aexp->makeup_gain;
290
291         const float tau = aexp->tau;
292
293         if (*aexp->enable <= 0) {
294                 ratio = 1.f;
295                 thresdb = 0.f;
296                 makeup = 0.f;
297                 makeup_target = 1.f;
298         }
299
300 #ifdef LV2_EXTENDED
301         if (aexp->v_knee != *aexp->knee) {
302                 aexp->v_knee = *aexp->knee;
303                 aexp->need_expose = true;
304         }
305
306         if (aexp->v_ratio != ratio) {
307                 aexp->v_ratio = ratio;
308                 aexp->need_expose = true;
309         }
310
311         if (aexp->v_thresdb != thresdb) {
312                 aexp->v_thresdb = thresdb;
313                 aexp->need_expose = true;
314         }
315
316         if (aexp->v_makeup != makeup) {
317                 aexp->v_makeup = makeup;
318                 aexp->need_expose = true;
319         }
320 #endif
321
322         aexp->v_gainr = 0.0;
323
324         for (i = 0; i < n_samples; i++) {
325                 in0 = input[i];
326                 sc0 = sc[i];
327                 ingain = usesidechain ? fabs(sc0) : fabs(in0);
328                 Lyg = 0.f;
329                 Lxg = (ingain==0.f) ? -160.f : to_dB(ingain);
330                 Lxg = sanitize_denormal(Lxg);
331
332                 if (2.f*(Lxg-thresdb) < -width) {
333                         Lyg = thresdb + (Lxg-thresdb) * ratio;
334                         Lyg = sanitize_denormal(Lyg);
335                 } else if (2.f*(Lxg-thresdb) > width) {
336                         Lyg = Lxg;
337                 } else {
338                         Lyg = Lxg + (1.f-ratio)*(Lxg-thresdb-width/2.f)*(Lxg-thresdb-width/2.f)/(2.f*width);
339                 }
340
341                 current_gainr = Lxg - Lyg;
342
343                 if (current_gainr > old_gainr) {
344                         current_gainr = release_coeff*old_gainr + (1.f-release_coeff)*current_gainr;
345                 } else if (current_gainr < old_gainr) {
346                         current_gainr = attack_coeff*old_gainr + (1.f-attack_coeff)*current_gainr;
347                 }
348
349                 current_gainr = sanitize_denormal(current_gainr);
350
351                 Lgain = from_dB(-current_gainr);
352
353                 old_gainr = current_gainr;
354
355                 if (current_gainr > aexp->v_gainr) {
356                         aexp->v_gainr = current_gainr;
357                 }
358
359                 lgaininp = in0 * Lgain;
360
361                 makeup_gain += tau * (makeup_target - makeup_gain) + 1e-12;
362                 output[i] = lgaininp * makeup_gain;
363
364                 max = (fabsf(output[i]) > max) ? fabsf(output[i]) : sanitize_denormal(max);
365         }
366
367         *(aexp->gainr) = current_gainr;
368         *(aexp->outlevel) = (max < 0.0056f) ? -45.f : to_dB(max);
369         aexp->makeup_gain = makeup_gain;
370
371 #ifdef LV2_EXTENDED
372         const float v_lvl_out = (max < 0.001f) ? -60.f : to_dB(max);
373         const float v_lvl_in = v_lvl_out > thresdb ?
374                 v_lvl_out :
375                 (aexp->v_lvl_out - thresdb * (1.f-ratio))/ratio;
376
377         if (fabsf (aexp->v_lvl_out - v_lvl_out) >= 1 || fabsf (aexp->v_lvl_in - v_lvl_in) >= 1) {
378                 // >= 1dB difference
379                 aexp->need_expose = true;
380                 aexp->v_lvl_in = v_lvl_in;
381                 const float relax_coef = exp(-(float)n_samples/srate);
382                 aexp->v_lvl_out = fmaxf (v_lvl_out, relax_coef*aexp->v_lvl_out + (1.f-relax_coef)*v_lvl_out);
383         }
384         if (aexp->need_expose && aexp->queue_draw) {
385                 aexp->need_expose = false;
386                 aexp->queue_draw->queue_draw (aexp->queue_draw->handle);
387         }
388 #endif
389 }
390
391
392 static void
393 run_stereo(LV2_Handle instance, uint32_t n_samples)
394 {
395         AExp* aexp = (AExp*)instance;
396
397         const float* const input0 = aexp->input0;
398         const float* const input1 = aexp->input1;
399         const float* const sc = aexp->sc;
400         float* const output0 = aexp->output0;
401         float* const output1 = aexp->output1;
402
403         float srate = aexp->srate;
404         float width = (6.f * *(aexp->knee)) + 0.01;
405         float attack_coeff = exp(-1000.f/(*(aexp->attack) * srate));
406         float release_coeff = exp(-1000.f/(*(aexp->release) * srate));
407
408         float max = 0.f;
409         float lgaininp = 0.f;
410         float rgaininp = 0.f;
411         float Lgain = 1.f;
412         float Lxg, Lyg;
413         float current_gainr;
414         float old_gainr = *aexp->gainr;
415
416         int usesidechain = (*(aexp->sidechain) <= 0.f) ? 0 : 1;
417         uint32_t i;
418         float ingain;
419         float in0;
420         float in1;
421         float sc0;
422         float maxabslr;
423
424         float ratio = *aexp->ratio;
425         float thresdb = *aexp->thresdb;
426         float makeup = *aexp->makeup;
427         float makeup_target = from_dB(makeup);
428         float makeup_gain = aexp->makeup_gain;
429
430         const float tau = aexp->tau;
431
432         if (*aexp->enable <= 0) {
433                 ratio = 1.f;
434                 thresdb = 0.f;
435                 makeup = 0.f;
436                 makeup_target = 1.f;
437         }
438
439 #ifdef LV2_EXTENDED
440         if (aexp->v_knee != *aexp->knee) {
441                 aexp->v_knee = *aexp->knee;
442                 aexp->need_expose = true;
443         }
444
445         if (aexp->v_ratio != ratio) {
446                 aexp->v_ratio = ratio;
447                 aexp->need_expose = true;
448         }
449
450         if (aexp->v_thresdb != thresdb) {
451                 aexp->v_thresdb = thresdb;
452                 aexp->need_expose = true;
453         }
454
455         if (aexp->v_makeup != makeup) {
456                 aexp->v_makeup = makeup;
457                 aexp->need_expose = true;
458         }
459 #endif
460
461         aexp->v_gainr = 0.0;
462
463         for (i = 0; i < n_samples; i++) {
464                 in0 = input0[i];
465                 in1 = input1[i];
466                 sc0 = sc[i];
467                 maxabslr = fmaxf(fabs(in0), fabs(in1));
468                 ingain = usesidechain ? fabs(sc0) : maxabslr;
469                 Lyg = 0.f;
470                 Lxg = (ingain==0.f) ? -160.f : to_dB(ingain);
471                 Lxg = sanitize_denormal(Lxg);
472
473                 if (2.f*(Lxg-thresdb) < -width) {
474                         Lyg = thresdb + (Lxg-thresdb) * ratio;
475                         Lyg = sanitize_denormal(Lyg);
476                 } else if (2.f*(Lxg-thresdb) > width) {
477                         Lyg = Lxg;
478                 } else {
479                         Lyg = Lxg + (1.f-ratio)*(Lxg-thresdb-width/2.f)*(Lxg-thresdb-width/2.f)/(2.f*width);
480                 }
481
482                 current_gainr = Lxg - Lyg;
483
484                 if (current_gainr > old_gainr) {
485                         current_gainr = release_coeff*old_gainr + (1.f-release_coeff)*current_gainr;
486                 } else if (current_gainr < old_gainr) {
487                         current_gainr = attack_coeff*old_gainr + (1.f-attack_coeff)*current_gainr;
488                 }
489
490                 current_gainr = sanitize_denormal(current_gainr);
491
492                 Lgain = from_dB(-current_gainr);
493
494                 old_gainr = current_gainr;
495
496                 if (current_gainr > aexp->v_gainr) {
497                         aexp->v_gainr = current_gainr;
498                 }
499
500                 lgaininp = in0 * Lgain;
501                 rgaininp = in1 * Lgain;
502
503                 makeup_gain += tau * (makeup_target - makeup_gain) + 1e-12;
504
505                 output0[i] = lgaininp * makeup_gain;
506                 output1[i] = rgaininp * makeup_gain;
507
508                 max = (fmaxf(fabs(output0[i]), fabs(output1[i])) > max) ? fmaxf(fabs(output0[i]), fabs(output1[i])) : sanitize_denormal(max);
509         }
510
511         *(aexp->gainr) = current_gainr;
512         *(aexp->outlevel) = (max < 0.0056f) ? -45.f : to_dB(max);
513         aexp->makeup_gain = makeup_gain;
514
515 #ifdef LV2_EXTENDED
516         const float v_lvl_out = (max < 0.001f) ? -60.f : to_dB(max);
517         const float v_lvl_in = v_lvl_out > thresdb ?
518                 v_lvl_out :
519                 (aexp->v_lvl_out - thresdb * (1.f-ratio))/ratio;
520
521         if (fabsf (aexp->v_lvl_out - v_lvl_out) >= 1 || fabsf (aexp->v_lvl_in - v_lvl_in) >= 1) {
522                 // >= 1dB difference
523                 aexp->need_expose = true;
524                 aexp->v_lvl_in = v_lvl_in;
525                 const float relax_coef = exp(-2.0*n_samples/srate);
526                 aexp->v_lvl_out = fmaxf (v_lvl_out, relax_coef*aexp->v_lvl_out + (1.f-relax_coef)*v_lvl_out);
527         }
528         if (aexp->need_expose && aexp->queue_draw) {
529                 aexp->need_expose = false;
530                 aexp->queue_draw->queue_draw (aexp->queue_draw->handle);
531         }
532 #endif
533 }
534
535
536 static void
537 deactivate(LV2_Handle instance)
538 {
539         activate(instance);
540 }
541
542 static void
543 cleanup(LV2_Handle instance)
544 {
545 #ifdef LV2_EXTENDED
546         AExp* aexp = (AExp*)instance;
547         if (aexp->display) {
548                 cairo_surface_destroy (aexp->display);
549         }
550 #endif
551
552         free(instance);
553 }
554
555
556 #ifndef MIN
557 #define MIN(A,B) ((A) < (B)) ? (A) : (B)
558 #endif
559
560 #ifdef LV2_EXTENDED
561 static float
562 exp_curve (const AExp* self, float xg) {
563         const float knee = self->v_knee;
564         const float ratio = self->v_ratio;
565         const float thresdb = self->v_thresdb;
566         const float makeup = self->v_makeup;
567
568         const float width = 6.f * knee + 0.01f;
569         float yg = 0.f;
570
571         if (2.f * (xg - thresdb) < -width) {
572                 yg = thresdb + (xg - thresdb) * ratio;
573         } else if (2.f * (xg - thresdb) > width) {
574                 yg = xg;
575         } else {
576                 yg = xg + (1.f - ratio) * (xg - thresdb - width / 2.f) * (xg - thresdb - width / 2.f) / (2.f * width);
577         }
578
579         yg += makeup;
580
581         return yg;
582 }
583
584
585 static void
586 render_inline_full (cairo_t* cr, const AExp* self)
587 {
588         const float w = self->w;
589         const float h = self->h;
590
591         const float makeup_thres = self->v_thresdb + self->v_makeup;
592
593         // clear background
594         cairo_rectangle (cr, 0, 0, w, h);
595         cairo_set_source_rgba (cr, .2, .2, .2, 1.0);
596         cairo_fill (cr);
597
598         cairo_set_line_width(cr, 1.0);
599
600         // draw grid 10dB steps
601         const double dash1[] = {1, 2};
602         const double dash2[] = {1, 3};
603         cairo_save (cr);
604         cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
605         cairo_set_dash(cr, dash2, 2, 2);
606         cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.5);
607
608         for (uint32_t d = 1; d < 7; ++d) {
609                 const float x = -.5 + floorf (w * (d * 10.f / 70.f));
610                 const float y = -.5 + floorf (h * (d * 10.f / 70.f));
611
612                 cairo_move_to (cr, x, 0);
613                 cairo_line_to (cr, x, h);
614                 cairo_stroke (cr);
615
616                 cairo_move_to (cr, 0, y);
617                 cairo_line_to (cr, w, y);
618                 cairo_stroke (cr);
619         }
620         cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 1.0);
621         cairo_set_dash(cr, dash1, 2, 2);
622         if (self->v_thresdb < 0) {
623                 const float y = -.5 + floorf (h * ((makeup_thres - 10.f) / -70.f));
624                 cairo_move_to (cr, 0, y);
625                 cairo_line_to (cr, w, y);
626                 cairo_stroke (cr);
627         }
628         // diagonal unity
629         cairo_move_to (cr, 0, h);
630         cairo_line_to (cr, w, 0);
631         cairo_stroke (cr);
632         cairo_restore (cr);
633
634         { // 0, 0
635                 cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.5);
636                 const float x = -.5 + floorf (w * (60.f / 70.f));
637                 const float y = -.5 + floorf (h * (10.f / 70.f));
638                 cairo_move_to (cr, x, 0);
639                 cairo_line_to (cr, x, h);
640                 cairo_stroke (cr);
641                 cairo_move_to (cr, 0, y);
642                 cairo_line_to (cr, w, y);
643                 cairo_stroke (cr);
644         }
645
646         { // GR
647                 const float x = -.5 + floorf (w * (62.5f / 70.f));
648                 const float y = -.5 + floorf (h * (10.0f / 70.f));
649                 const float wd = floorf (w * (5.f / 70.f));
650                 const float ht = floorf (h * (55.f / 70.f));
651                 cairo_rectangle (cr, x, y, wd, ht);
652                 cairo_fill (cr);
653
654                 const float h_gr = fminf (ht, floorf (h * self->v_gainr / 70.f));
655                 cairo_set_source_rgba (cr, 0.95, 0.0, 0.0, 1.0);
656                 cairo_rectangle (cr, x, y, wd, h_gr);
657                 cairo_fill (cr);
658                 cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.5);
659                 cairo_rectangle (cr, x, y, wd, ht);
660                 cairo_set_source_rgba (cr, 0.75, 0.75, 0.75, 1.0);
661                 cairo_stroke (cr);
662         }
663
664         // draw curve
665         cairo_set_source_rgba (cr, .8, .8, .8, 1.0);
666         cairo_move_to (cr, 0, h);
667
668         for (uint32_t x = 0; x < w; ++x) {
669                 // plot -60..+10  dB
670                 const float x_db = 70.f * (-1.f + x / (float)w) + 10.f;
671                 const float y_db = exp_curve (self, x_db) - 10.f;
672                 const float y = h * (y_db / -70.f);
673                 cairo_line_to (cr, x, y);
674         }
675         cairo_stroke_preserve (cr);
676
677         cairo_line_to (cr, w, h);
678         cairo_close_path (cr);
679         cairo_clip (cr);
680
681         // draw signal level & reduction/gradient
682         const float top = exp_curve (self, 0) - 10.f;
683         cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, h);
684         if (top > makeup_thres - 10.f) {
685                 cairo_pattern_add_color_stop_rgba (pat, 0.0, 0.8, 0.1, 0.1, 0.5);
686                 cairo_pattern_add_color_stop_rgba (pat, top / -70.f, 0.8, 0.1, 0.1, 0.5);
687         }
688         if (self->v_knee > 0) {
689                 cairo_pattern_add_color_stop_rgba (pat, ((makeup_thres -10.f) / -70.f), 0.7, 0.7, 0.2, 0.5);
690                 cairo_pattern_add_color_stop_rgba (pat, ((makeup_thres - self->v_knee - 10.f) / -70.f), 0.5, 0.5, 0.5, 0.5);
691         } else {
692                 cairo_pattern_add_color_stop_rgba (pat, ((makeup_thres - 10.f)/ -70.f), 0.7, 0.7, 0.2, 0.5);
693                 cairo_pattern_add_color_stop_rgba (pat, ((makeup_thres - 10.01f) / -70.f), 0.5, 0.5, 0.5, 0.5);
694         }
695         cairo_pattern_add_color_stop_rgba (pat, 1.0, 0.5, 0.5, 0.5, 0.5);
696
697         // maybe cut off at x-position?
698         const float x = w * (self->v_lvl_in + 60) / 70.f;
699         const float y = x + h*self->v_makeup;
700         cairo_rectangle (cr, 0, h - y, x, y);
701         if (self->v_ratio > 1.0) {
702                 cairo_set_source (cr, pat);
703         } else {
704                 cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.5);
705         }
706         cairo_fill (cr);
707
708         cairo_pattern_destroy (pat); // TODO cache pattern
709 }
710
711 static void
712 render_inline_only_bars (cairo_t* cr, const AExp* self)
713 {
714         const float w = self->w;
715         const float h = self->h;
716
717         cairo_rectangle (cr, 0, 0, w, h);
718         cairo_set_source_rgba (cr, .2, .2, .2, 1.0);
719         cairo_fill (cr);
720
721
722         cairo_save (cr);
723
724         const float ht = 0.25f * h;
725
726         const float x1 = w*0.05;
727         const float wd = w - 2.0f*x1;
728
729         const float y1 = 0.17*h;
730         const float y2 = h - y1 - ht;
731
732         cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.5);
733
734         cairo_rectangle (cr, x1, y1, wd, ht);
735         cairo_fill (cr);
736
737         cairo_rectangle (cr, x1, y2, wd, ht);
738         cairo_fill (cr);
739
740         cairo_set_source_rgba (cr, 0.75, 0.0, 0.0, 1.0);
741         const float w_gr = (self->v_gainr > 60.f) ? wd : wd * self->v_gainr * (1.f/60.f);
742         cairo_rectangle (cr, x1+wd-w_gr, y2, w_gr, ht);
743         cairo_fill (cr);
744
745         if (self->v_lvl_out > -60.f) {
746                 if (self->v_lvl_out > 10.f) {
747                         cairo_set_source_rgba (cr, 0.75, 0.0, 0.0, 1.0);
748                 } else if (self->v_lvl_out > 0.f) {
749                         cairo_set_source_rgba (cr, 0.66, 0.66, 0.0, 1.0);
750                 } else {
751                         cairo_set_source_rgba (cr, 0.0, 0.66, 0.0, 1.0);
752                 }
753                 const float w_g = (self->v_lvl_out > 10.f) ? wd : wd * (60.f+self->v_lvl_out) / 70.f;
754                 cairo_rectangle (cr, x1, y1, w_g, ht);
755                 cairo_fill (cr);
756         }
757
758         cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1.0);
759
760         const float tck = 0.33*ht;
761
762         cairo_set_line_width (cr, .5);
763
764         for (uint32_t d = 1; d < 7; ++d) {
765                 const float x = x1 + (d * wd * (10.f / 70.f));
766
767                 cairo_move_to (cr, x, y1);
768                 cairo_line_to (cr, x, y1+tck);
769
770                 cairo_move_to (cr, x, y1+ht);
771                 cairo_line_to (cr, x, y1+ht-tck);
772
773                 cairo_move_to (cr, x, y2);
774                 cairo_line_to (cr, x, y2+tck);
775
776                 cairo_move_to (cr, x, y2+ht);
777                 cairo_line_to (cr, x, y2+ht-tck);
778         }
779
780         cairo_stroke (cr);
781
782         const float x_0dB = x1 + wd*(60.f/70.f);
783
784         cairo_move_to (cr, x_0dB, y1);
785         cairo_line_to (cr, x_0dB, y1+ht);
786
787         cairo_rectangle (cr, x1, y1, wd, ht);
788         cairo_rectangle (cr, x1, y2, wd, ht);
789         cairo_stroke (cr);
790
791         cairo_set_line_width (cr, 2.0);
792
793         // visualize threshold
794         const float tr = x1 + wd * (60.f+self->v_thresdb) / 70.f;
795         cairo_set_source_rgba (cr, 0.95, 0.95, 0.0, 1.0);
796         cairo_move_to (cr, tr, y1);
797         cairo_line_to (cr, tr, y1+ht);
798         cairo_stroke (cr);
799
800         // visualize ratio
801         const float reduced_0dB = self->v_thresdb * (1.f - 1.f/self->v_ratio);
802         const float rt = x1 + wd * (60.f+reduced_0dB) / 70.f;
803         cairo_set_source_rgba (cr, 0.95, 0.0, 0.0, 1.0);
804         cairo_move_to (cr, rt, y1);
805         cairo_line_to (cr, rt, y1+ht);
806         cairo_stroke (cr);
807 }
808
809 static LV2_Inline_Display_Image_Surface *
810 render_inline (LV2_Handle instance, uint32_t w, uint32_t max_h)
811 {
812         AExp* self = (AExp*)instance;
813
814         uint32_t h = MIN (w, max_h);
815         if (w < 200) {
816                 h = 40;
817         }
818
819         if (!self->display || self->w != w || self->h != h) {
820                 if (self->display) cairo_surface_destroy(self->display);
821                 self->display = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
822                 self->w = w;
823                 self->h = h;
824         }
825
826         cairo_t* cr = cairo_create (self->display);
827
828         if (w >= 200) {
829                 render_inline_full (cr, self);
830         } else {
831                 render_inline_only_bars (cr, self);
832         }
833
834         cairo_destroy (cr);
835
836         cairo_surface_flush (self->display);
837         self->surf.width = cairo_image_surface_get_width (self->display);
838         self->surf.height = cairo_image_surface_get_height (self->display);
839         self->surf.stride = cairo_image_surface_get_stride (self->display);
840         self->surf.data = cairo_image_surface_get_data  (self->display);
841
842         return &self->surf;
843 }
844 #endif
845
846 static const void*
847 extension_data(const char* uri)
848 {
849 #ifdef LV2_EXTENDED
850         static const LV2_Inline_Display_Interface display  = { render_inline };
851         if (!strcmp(uri, LV2_INLINEDISPLAY__interface)) {
852                 return &display;
853         }
854 #endif
855         return NULL;
856 }
857
858 static const LV2_Descriptor descriptor_mono = {
859         AEXP_URI,
860         instantiate,
861         connect_mono,
862         activate,
863         run_mono,
864         deactivate,
865         cleanup,
866         extension_data
867 };
868
869 static const LV2_Descriptor descriptor_stereo = {
870         AEXP_STEREO_URI,
871         instantiate,
872         connect_stereo,
873         activate,
874         run_stereo,
875         deactivate,
876         cleanup,
877         extension_data
878 };
879
880 LV2_SYMBOL_EXPORT
881 const LV2_Descriptor*
882 lv2_descriptor(uint32_t index)
883 {
884         switch (index) {
885         case 0:
886                 return &descriptor_mono;
887         case 1:
888                 return &descriptor_stereo;
889         default:
890                 return NULL;
891         }
892 }