2 * Copyright (C) 2016 Damien Zammit <damien@zamaudio.com>
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.
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.
21 #include <cairo/cairo.h>
22 #include "ardour/lv2_extensions.h"
25 #include "lv2/lv2plug.in/ns/lv2core/lv2.h"
27 #define ACOMP_URI "urn:ardour:a-comp"
70 LV2_Inline_Display_Image_Surface surf;
71 cairo_surface_t* display;
72 LV2_Inline_Display* queue_draw;
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
87 instantiate(const LV2_Descriptor* descriptor,
89 const char* bundle_path,
90 const LV2_Feature* const* features)
92 AComp* acomp = (AComp*)calloc(1, sizeof(AComp));
94 for (int i=0; features[i]; ++i) {
96 if (!strcmp(features[i]->URI, LV2_INLINEDISPLAY__queue_draw)) {
97 acomp->queue_draw = (LV2_Inline_Display*) features[i]->data;
103 acomp->old_yl=acomp->old_y1=acomp->old_yg=0.f;
104 acomp->need_expose = true;
106 return (LV2_Handle)acomp;
110 connect_port(LV2_Handle instance,
114 AComp* acomp = (AComp*)instance;
116 switch ((PortIndex)port) {
118 acomp->attack = (float*)data;
121 acomp->release = (float*)data;
124 acomp->knee = (float*)data;
127 acomp->ratio = (float*)data;
129 case ACOMP_THRESHOLD:
130 acomp->thresdb = (float*)data;
133 acomp->makeup = (float*)data;
136 acomp->gainr = (float*)data;
139 acomp->outlevel = (float*)data;
141 case ACOMP_SIDECHAIN:
142 acomp->sidechain = (float*)data;
145 acomp->input0 = (float*)data;
148 acomp->input1 = (float*)data;
151 acomp->output = (float*)data;
156 // Force already-denormal float value to zero
158 sanitize_denormal(float value) {
159 if (!isnormal(value)) {
167 return (exp(gdb/20.f*log(10.f)));
172 return (20.f*log10(g));
176 activate(LV2_Handle instance)
178 AComp* acomp = (AComp*)instance;
180 *(acomp->gainr) = 0.0f;
181 *(acomp->outlevel) = -45.0f;
182 acomp->old_yl=acomp->old_y1=acomp->old_yg=0.f;
186 run(LV2_Handle instance, uint32_t n_samples)
188 AComp* acomp = (AComp*)instance;
190 const float* const input0 = acomp->input0;
191 const float* const input1 = acomp->input1;
192 float* const output = acomp->output;
194 float srate = acomp->srate;
195 float width = (6.f * *(acomp->knee)) + 0.01;
197 float attack_coeff = exp(-1000.f/(*(acomp->attack) * srate));
198 float release_coeff = exp(-1000.f/(*(acomp->release) * srate));
201 float lgaininp = 0.f;
203 float Lxg, Lxl, Lyg, Lyl, Ly1;
204 int usesidechain = (*(acomp->sidechain) < 0.5) ? 0 : 1;
209 float ratio = *(acomp->ratio);
210 float thresdb = *(acomp->thresdb);
213 if (acomp->v_knee != *acomp->knee) {
214 acomp->v_knee = *acomp->knee;
215 acomp->need_expose = true;
218 if (acomp->v_ratio != *acomp->ratio) {
219 acomp->v_ratio = *acomp->ratio;
220 acomp->need_expose = true;
223 if (acomp->v_thresdb != *acomp->thresdb) {
224 acomp->v_thresdb = *acomp->thresdb;
225 acomp->need_expose = true;
229 for (i = 0; i < n_samples; i++) {
232 ingain = usesidechain ? in1 : in0;
234 Lxg = (ingain==0.f) ? -160.f : to_dB(fabs(ingain));
235 Lxg = sanitize_denormal(Lxg);
237 Lyg = Lxg + (1.f/ratio-1.f)*(Lxg-thresdb+width/2.f)*(Lxg-thresdb+width/2.f)/(2.f*width);
239 if (2.f*(Lxg-thresdb) < -width) {
242 Lyg = thresdb + (Lxg-thresdb)/ratio;
243 Lyg = sanitize_denormal(Lyg);
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);
256 Lgain = from_dB(cdb);
258 *(acomp->gainr) = Lyl;
260 lgaininp = in0 * Lgain;
261 output[i] = lgaininp * from_dB(*(acomp->makeup));
263 max = (fabsf(output[i]) > max) ? fabsf(output[i]) : sanitize_denormal(max);
265 // TODO re-use local variables on stack
266 // store values back to acomp at the end of the inner-loop
272 *(acomp->outlevel) = (max < 0.0056f) ? -45.f : to_dB(max);
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;
280 if (acomp->need_expose && acomp->queue_draw) {
281 acomp->need_expose = false;
282 acomp->queue_draw->queue_draw (acomp->queue_draw->handle);
288 deactivate(LV2_Handle instance)
294 cleanup(LV2_Handle instance)
297 AComp* acomp = (AComp*)instance;
298 if (acomp->display) {
299 cairo_surface_destroy (acomp->display);
308 #define MIN(A,B) ((A) < (B)) ? (A) : (B)
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;
318 const float width = 6.f * knee + 0.01f;
321 if (2.f * (xg - thresdb) < -width) {
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;
332 static LV2_Inline_Display_Image_Surface *
333 render_inline (LV2_Handle instance, uint32_t w, uint32_t max_h)
335 AComp* self = (AComp*)instance;
336 uint32_t h = MIN (w, max_h);
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);
345 cairo_t* cr = cairo_create (self->display);
348 cairo_rectangle (cr, 0, 0, w, h);
349 cairo_set_source_rgba (cr, .2, .2, .2, 1.0);
352 cairo_set_line_width(cr, 1.0);
354 // draw grid 10dB steps
355 const double dash2[] = {1, 3};
357 cairo_set_dash(cr, dash2, 2, 2);
358 cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.5);
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));
364 cairo_move_to (cr, x, 0);
365 cairo_line_to (cr, x, h);
368 cairo_move_to (cr, 0, y);
369 cairo_line_to (cr, w, y);
376 cairo_set_source_rgba (cr, .8, .8, .8, 1.0);
377 cairo_move_to (cr, 0, h);
379 for (uint32_t x = 0; x < w; ++x) {
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);
386 cairo_stroke_preserve (cr);
388 cairo_line_to (cr, w, h);
389 cairo_close_path (cr);
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);
401 // create RGBA surface
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);
414 extension_data(const char* uri)
416 static const LV2_Inline_Display_Interface display = { render_inline };
417 if (!strcmp(uri, LV2_INLINEDISPLAY__interface)) {
423 static const LV2_Descriptor descriptor = {
435 const LV2_Descriptor*
436 lv2_descriptor(uint32_t index)