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