display level in a-comp display
[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
29 typedef enum {
30         ACOMP_INPUT0 = 0,
31         ACOMP_INPUT1,
32         ACOMP_OUTPUT,
33
34         ACOMP_ATTACK,
35         ACOMP_RELEASE,
36         ACOMP_KNEE,
37         ACOMP_RATIO,
38         ACOMP_THRESHOLD,
39         ACOMP_MAKEUP,
40
41         ACOMP_GAINR,
42         ACOMP_OUTLEVEL,
43         ACOMP_SIDECHAIN,
44 } PortIndex;
45
46
47 typedef struct {
48         float* input0;
49         float* input1;
50         float* output;
51
52         float* attack;
53         float* release;
54         float* knee;
55         float* ratio;
56         float* thresdb;
57         float* makeup;
58
59         float* gainr;
60         float* outlevel;
61         float* sidechain;
62
63         float srate;
64         float old_yl;
65         float old_y1;
66         float old_yg;
67
68 #ifdef LV2_EXTENDED
69         bool                     need_expose;
70         LV2_Inline_Display_Image_Surface surf;
71         cairo_surface_t*         display;
72         LV2_Inline_Display*      queue_draw;
73         uint32_t                 w, h;
74
75         /* ports pointers are only valid during run so we'll
76          * have to cache them for the display, besides
77          * we do want to check for changes
78          */
79         float v_knee;
80         float v_ratio;
81         float v_thresdb;
82         float v_lvl;
83 #endif
84 } AComp;
85
86 static LV2_Handle
87 instantiate(const LV2_Descriptor* descriptor,
88             double rate,
89             const char* bundle_path,
90             const LV2_Feature* const* features)
91 {
92         AComp* acomp = (AComp*)calloc(1, sizeof(AComp));
93
94         for (int i=0; features[i]; ++i) {
95 #ifdef LV2_EXTENDED
96                 if (!strcmp(features[i]->URI, LV2_INLINEDISPLAY__queue_draw)) {
97                         acomp->queue_draw = (LV2_Inline_Display*) features[i]->data;
98                 }
99 #endif
100         }
101
102         acomp->srate = rate;
103         acomp->old_yl=acomp->old_y1=acomp->old_yg=0.f;
104         acomp->need_expose = true;
105
106         return (LV2_Handle)acomp;
107 }
108
109 static void
110 connect_port(LV2_Handle instance,
111              uint32_t port,
112              void* data)
113 {
114         AComp* acomp = (AComp*)instance;
115
116         switch ((PortIndex)port) {
117         case ACOMP_ATTACK:
118                 acomp->attack = (float*)data;
119                 break;
120         case ACOMP_RELEASE:
121                 acomp->release = (float*)data;
122                 break;
123         case ACOMP_KNEE:
124                 acomp->knee = (float*)data;
125                 break;
126         case ACOMP_RATIO:
127                 acomp->ratio = (float*)data;
128                 break;
129         case ACOMP_THRESHOLD:
130                 acomp->thresdb = (float*)data;
131                 break;
132         case ACOMP_MAKEUP:
133                 acomp->makeup = (float*)data;
134                 break;
135         case ACOMP_GAINR:
136                 acomp->gainr = (float*)data;
137                 break;
138         case ACOMP_OUTLEVEL:
139                 acomp->outlevel = (float*)data;
140                 break;
141         case ACOMP_SIDECHAIN:
142                 acomp->sidechain = (float*)data;
143                 break;
144         case ACOMP_INPUT0:
145                 acomp->input0 = (float*)data;
146                 break;
147         case ACOMP_INPUT1:
148                 acomp->input1 = (float*)data;
149                 break;
150         case ACOMP_OUTPUT:
151                 acomp->output = (float*)data;
152                 break;
153         }
154 }
155
156 // Force already-denormal float value to zero
157 static inline float
158 sanitize_denormal(float value) {
159         if (!isnormal(value)) {
160                 value = 0.f;
161         }
162         return value;
163 }
164
165 static inline float
166 from_dB(float gdb) {
167         return (exp(gdb/20.f*log(10.f)));
168 }
169
170 static inline float
171 to_dB(float g) {
172         return (20.f*log10(g));
173 }
174
175 static void
176 activate(LV2_Handle instance)
177 {
178         AComp* acomp = (AComp*)instance;
179
180         *(acomp->gainr) = 0.0f;
181         *(acomp->outlevel) = -45.0f;
182         acomp->old_yl=acomp->old_y1=acomp->old_yg=0.f;
183 }
184
185 static void
186 run(LV2_Handle instance, uint32_t n_samples)
187 {
188         AComp* acomp = (AComp*)instance;
189
190         const float* const input0 = acomp->input0;
191         const float* const input1 = acomp->input1;
192         float* const output = acomp->output;
193
194         float srate = acomp->srate;
195         float width = (6.f * *(acomp->knee)) + 0.01;
196         float cdb=0.f;
197         float attack_coeff = exp(-1000.f/(*(acomp->attack) * srate));
198         float release_coeff = exp(-1000.f/(*(acomp->release) * srate));
199
200         float max = 0.f;
201         float lgaininp = 0.f;
202         float Lgain = 1.f;
203         float Lxg, Lxl, Lyg, Lyl, Ly1;
204         int usesidechain = (*(acomp->sidechain) < 0.5) ? 0 : 1;
205         uint32_t i;
206         float ingain;
207         float in0;
208         float in1;
209         float ratio = *(acomp->ratio);
210         float thresdb = *(acomp->thresdb);
211
212 #ifdef LV2_EXTENDED
213         if (acomp->v_knee != *acomp->knee) {
214                 acomp->v_knee = *acomp->knee;
215                 acomp->need_expose = true;
216         }
217
218         if (acomp->v_ratio != *acomp->ratio) {
219                 acomp->v_ratio = *acomp->ratio;
220                 acomp->need_expose = true;
221         }
222
223         if (acomp->v_thresdb != *acomp->thresdb) {
224                 acomp->v_thresdb = *acomp->thresdb;
225                 acomp->need_expose = true;
226         }
227 #endif
228
229         for (i = 0; i < n_samples; i++) {
230                 in0 = input0[i];
231                 in1 = input1[i];
232                 ingain = usesidechain ? in1 : in0;
233                 Lyg = 0.f;
234                 Lxg = (ingain==0.f) ? -160.f : to_dB(fabs(ingain));
235                 Lxg = sanitize_denormal(Lxg);
236
237                 Lyg = Lxg + (1.f/ratio-1.f)*(Lxg-thresdb+width/2.f)*(Lxg-thresdb+width/2.f)/(2.f*width);
238
239                 if (2.f*(Lxg-thresdb) < -width) {
240                         Lyg = Lxg;
241                 } else {
242                         Lyg = thresdb + (Lxg-thresdb)/ratio;
243                         Lyg = sanitize_denormal(Lyg);
244                 }
245
246                 Lxl = Lxg - Lyg;
247
248                 acomp->old_y1 = sanitize_denormal(acomp->old_y1);
249                 acomp->old_yl = sanitize_denormal(acomp->old_yl);
250                 Ly1 = fmaxf(Lxl, release_coeff * acomp->old_y1+(1.f-release_coeff)*Lxl);
251                 Lyl = attack_coeff * acomp->old_yl+(1.f-attack_coeff)*Ly1;
252                 Ly1 = sanitize_denormal(Ly1);
253                 Lyl = sanitize_denormal(Lyl);
254
255                 cdb = -Lyl;
256                 Lgain = from_dB(cdb);
257
258                 *(acomp->gainr) = Lyl;
259
260                 lgaininp = in0 * Lgain;
261                 output[i] = lgaininp * from_dB(*(acomp->makeup));
262
263                 max = (fabsf(output[i]) > max) ? fabsf(output[i]) : sanitize_denormal(max);
264
265                 // TODO re-use local variables on stack
266                 // store values back to acomp at the end of the inner-loop
267                 acomp->old_yl = Lyl;
268                 acomp->old_y1 = Ly1;
269                 acomp->old_yg = Lyg;
270         }
271
272         *(acomp->outlevel) = (max < 0.0056f) ? -45.f : to_dB(max);
273
274 #ifdef LV2_EXTENDED
275         const float v_lvl = (max < 0.001f) ? -60.f : to_dB(max);
276         if (fabsf (acomp->v_lvl - v_lvl) >= 1) { // quantize to 1dB difference
277                 acomp->need_expose = true;
278                 acomp->v_lvl = v_lvl;
279         }
280         if (acomp->need_expose && acomp->queue_draw) {
281                 acomp->need_expose = false;
282                 acomp->queue_draw->queue_draw (acomp->queue_draw->handle);
283         }
284 #endif
285 }
286
287 static void
288 deactivate(LV2_Handle instance)
289 {
290         activate(instance);
291 }
292
293 static void
294 cleanup(LV2_Handle instance)
295 {
296 #ifdef LV2_EXTENDED
297         AComp* acomp = (AComp*)instance;
298         if (acomp->display) {
299                 cairo_surface_destroy (acomp->display);
300         }
301 #endif
302
303         free(instance);
304 }
305
306
307 #ifndef MIN
308 #define MIN(A,B) ((A) < (B)) ? (A) : (B)
309 #endif
310
311 #ifdef LV2_EXTENDED
312 static float
313 comp_curve (AComp* self, float xg) {
314         const float knee = self->v_knee;
315         const float ratio = self->v_ratio;
316         const float thresdb = self->v_thresdb;
317
318         const float width = 6.f * knee + 0.01f;
319         float yg = 0.f;
320
321         if (2.f * (xg - thresdb) < -width) {
322                 yg = xg;
323         } else if (2.f * fabs (xg - thresdb) <= width) {
324                 yg = xg + (1.f / ratio - 1.f ) * (xg - thresdb + width / 2.f) * (xg - thresdb + width / 2.f) / (2.f * width);
325         } else if (2.f * (xg - thresdb) > width) {
326                 yg = thresdb + (xg - thresdb) / ratio;
327         }
328         return yg;
329 }
330
331
332 static LV2_Inline_Display_Image_Surface *
333 render_inline (LV2_Handle instance, uint32_t w, uint32_t max_h)
334 {
335         AComp* self = (AComp*)instance;
336         uint32_t h = MIN (w, max_h);
337
338         if (!self->display || self->w != w || self->h != h) {
339                 if (self->display) cairo_surface_destroy(self->display);
340                 self->display = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
341                 self->w = w;
342                 self->h = h;
343         }
344
345         cairo_t* cr = cairo_create (self->display);
346
347         // clear background
348         cairo_rectangle (cr, 0, 0, w, h);
349         cairo_set_source_rgba (cr, .2, .2, .2, 1.0);
350         cairo_fill (cr);
351
352         cairo_set_line_width(cr, 1.0);
353
354         // draw grid 10dB steps
355         const double dash2[] = {1, 3};
356         cairo_save (cr);
357         cairo_set_dash(cr, dash2, 2, 2);
358         cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.5);
359
360         for (uint32_t d = 1; d < 6; ++d) {
361                 const float x = -.5 + floorf (w * (d * 10.f / 60.f));
362                 const float y = -.5 + floorf (h * (d * 10.f / 60.f));
363
364                 cairo_move_to (cr, x, 0);
365                 cairo_line_to (cr, x, h);
366                 cairo_stroke (cr);
367
368                 cairo_move_to (cr, 0, y);
369                 cairo_line_to (cr, w, y);
370                 cairo_stroke (cr);
371         }
372         cairo_restore (cr);
373
374
375         // draw curve
376         cairo_set_source_rgba (cr, .8, .8, .8, 1.0);
377         cairo_move_to (cr, 0, h);
378
379         for (uint32_t x = 0; x < w; ++x) {
380                 // plot -60..0  dB
381                 const float x_db = 60.f * (-1.f + x / (float)w);
382                 const float y_db = comp_curve (self, x_db);
383                 const float y = h * (y_db / -60.f);
384                 cairo_line_to (cr, x, y);
385         }
386         cairo_stroke_preserve (cr);
387
388         cairo_line_to (cr, w, h);
389         cairo_close_path (cr);
390         cairo_clip (cr);
391
392         // draw signal level
393         // TODO add a gradient pattern above threshold
394         // maybe cut off at x-position?
395         const float y = h * (self->v_lvl + 60) / 60.f;
396         cairo_rectangle (cr, 0, h - y, w, y);
397         cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.5);
398         cairo_fill (cr);
399
400
401         // create RGBA surface
402         cairo_destroy (cr);
403         cairo_surface_flush (self->display);
404         self->surf.width = cairo_image_surface_get_width (self->display);
405         self->surf.height = cairo_image_surface_get_height (self->display);
406         self->surf.stride = cairo_image_surface_get_stride (self->display);
407         self->surf.data = cairo_image_surface_get_data  (self->display);
408
409         return &self->surf;
410 }
411 #endif
412
413 static const void*
414 extension_data(const char* uri)
415 {
416         static const LV2_Inline_Display_Interface display  = { render_inline };
417         if (!strcmp(uri, LV2_INLINEDISPLAY__interface)) {
418                 return &display;
419         }
420         return NULL;
421 }
422
423 static const LV2_Descriptor descriptor = {
424         ACOMP_URI,
425         instantiate,
426         connect_port,
427         activate,
428         run,
429         deactivate,
430         cleanup,
431         extension_data
432 };
433
434 LV2_SYMBOL_EXPORT
435 const LV2_Descriptor*
436 lv2_descriptor(uint32_t index)
437 {
438         switch (index) {
439         case 0:
440                 return &descriptor;
441         default:
442                 return NULL;
443         }
444 }