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