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"
28 #define ACOMP_STEREO_URI "urn:ardour:a-comp#stereo"
30 #define RESET_PEAK_AFTER_SECONDS 3
33 # define M_PI 3.14159265358979323846
38 #define isfinite_local(val) (bool)_finite((double)val)
40 #define isfinite_local isfinite
44 # define FLT_EPSILON 1.192093e-07
61 ACOMP_FULL_INLINEDISP,
84 float* full_inline_display;
99 LV2_Inline_Display_Image_Surface surf;
101 cairo_surface_t* display;
102 LV2_Inline_Display* queue_draw;
105 /* ports pointers are only valid during run so we'll
106 * have to cache them for the display, besides
107 * we do want to check for changes
118 bool v_full_inline_display;
121 uint32_t peakdb_samples;
126 instantiate(const LV2_Descriptor* descriptor,
128 const char* bundle_path,
129 const LV2_Feature* const* features)
131 AComp* acomp = (AComp*)calloc(1, sizeof(AComp));
133 if (!strcmp (descriptor->URI, ACOMP_URI)) {
134 acomp->n_channels = 1;
135 } else if (!strcmp (descriptor->URI, ACOMP_STEREO_URI)) {
136 acomp->n_channels = 2;
142 for (int i=0; features[i]; ++i) {
144 if (!strcmp(features[i]->URI, LV2_INLINEDISPLAY__queue_draw)) {
145 acomp->queue_draw = (LV2_Inline_Display*) features[i]->data;
151 acomp->makeup_gain = 1.f;
153 acomp->need_expose = true;
154 acomp->v_lvl_out = -70.f;
157 return (LV2_Handle)acomp;
161 connect_port(LV2_Handle instance,
165 AComp* acomp = (AComp*)instance;
167 switch ((PortIndex)port) {
169 acomp->attack = (float*)data;
172 acomp->release = (float*)data;
175 acomp->knee = (float*)data;
178 acomp->ratio = (float*)data;
180 case ACOMP_THRESHOLD:
181 acomp->thresdb = (float*)data;
184 acomp->makeup = (float*)data;
187 acomp->gainr = (float*)data;
190 acomp->outlevel = (float*)data;
193 acomp->inlevel = (float*)data;
195 case ACOMP_SIDECHAIN:
196 acomp->sidechain = (float*)data;
199 acomp->enable = (float*)data;
201 case ACOMP_FULL_INLINEDISP:
202 acomp->full_inline_display = (float*)data;
210 connect_mono(LV2_Handle instance,
214 AComp* acomp = (AComp*)instance;
215 connect_port (instance, port, data);
217 switch ((PortIndex)port) {
219 acomp->input0 = (float*)data;
222 acomp->sc = (float*)data;
225 acomp->output0 = (float*)data;
233 connect_stereo(LV2_Handle instance,
237 AComp* acomp = (AComp*)instance;
238 connect_port (instance, port, data);
240 switch ((PortIndex)port) {
242 acomp->input0 = (float*)data;
245 acomp->input1 = (float*)data;
248 acomp->sc = (float*)data;
251 acomp->output0 = (float*)data;
254 acomp->output1 = (float*)data;
261 // Force already-denormal float value to zero
263 sanitize_denormal(float value) {
264 if (!isnormal(value)) {
272 return (exp(gdb/20.f*log(10.f)));
277 return (20.f*log10(g));
281 activate(LV2_Handle instance)
283 AComp* acomp = (AComp*)instance;
285 *(acomp->gainr) = 0.0f;
286 *(acomp->outlevel) = -70.0f;
287 *(acomp->inlevel) = -160.f;
290 acomp->v_peakdb = -160.f;
291 acomp->peakdb_samples = 0;
296 run(LV2_Handle instance, uint32_t n_samples)
298 AComp* acomp = (AComp*)instance;
300 const float* const ins[2] = { acomp->input0, acomp->input1 };
301 const float* const sc = acomp->sc;
302 float* const outs[2] = { acomp->output0, acomp->output1 };
304 float srate = acomp->srate;
305 float width = (6.f * *(acomp->knee)) + 0.01;
306 float attack_coeff = exp(-1000.f/(*(acomp->attack) * srate));
307 float release_coeff = exp(-1000.f/(*(acomp->release) * srate));
313 float old_gainr = *acomp->gainr;
315 int usesidechain = (*(acomp->sidechain) <= 0.f) ? 0 : 1;
321 uint32_t n_channels = acomp->n_channels;
323 float ratio = *acomp->ratio;
324 float thresdb = *acomp->thresdb;
325 float makeup = *acomp->makeup;
326 float makeup_target = from_dB(makeup);
327 float makeup_gain = acomp->makeup_gain;
329 const float tau = (1.0 - exp (-2.f * M_PI * 25.f / acomp->srate));
331 if (*acomp->enable <= 0) {
339 if (acomp->v_knee != *acomp->knee) {
340 acomp->v_knee = *acomp->knee;
341 acomp->need_expose = true;
344 if (acomp->v_ratio != ratio) {
345 acomp->v_ratio = ratio;
346 acomp->need_expose = true;
349 if (acomp->v_thresdb != thresdb) {
350 acomp->v_thresdb = thresdb;
351 acomp->need_expose = true;
354 if (acomp->v_makeup != makeup) {
355 acomp->v_makeup = makeup;
356 acomp->need_expose = true;
359 bool full_inline = *acomp->full_inline_display > 0.5;
360 if (full_inline != acomp->v_full_inline_display) {
361 acomp->v_full_inline_display = full_inline;
362 acomp->need_expose = true;
366 float in_peak_db = -160.f;
367 float max_gainr = 0.f;
369 for (i = 0; i < n_samples; i++) {
371 for (uint32_t c=0; c<n_channels; ++c) {
372 maxabs = fmaxf(fabsf(ins[c][i]), maxabs);
375 ingain = usesidechain ? fabs(sc0) : maxabs;
377 Lxg = (ingain==0.f) ? -160.f : to_dB(ingain);
378 Lxg = sanitize_denormal(Lxg);
379 if (Lxg > in_peak_db) {
383 if (2.f*(Lxg-thresdb) < -width) {
385 } else if (2.f*(Lxg-thresdb) > width) {
386 Lyg = thresdb + (Lxg-thresdb)/ratio;
387 Lyg = sanitize_denormal(Lyg);
389 Lyg = Lxg + (1.f/ratio-1.f)*(Lxg-thresdb+width/2.f)*(Lxg-thresdb+width/2.f)/(2.f*width);
392 current_gainr = Lxg - Lyg;
394 if (current_gainr < old_gainr) {
395 current_gainr = release_coeff*old_gainr + (1.f-release_coeff)*current_gainr;
396 } else if (current_gainr > old_gainr) {
397 current_gainr = attack_coeff*old_gainr + (1.f-attack_coeff)*current_gainr;
400 current_gainr = sanitize_denormal(current_gainr);
402 Lgain = from_dB(-current_gainr);
404 old_gainr = current_gainr;
406 *(acomp->gainr) = current_gainr;
407 if (current_gainr > max_gainr) {
408 max_gainr = current_gainr;
411 makeup_gain += tau * (makeup_target - makeup_gain);
413 for (uint32_t c=0; c<n_channels; ++c) {
414 float out = ins[c][i] * Lgain * makeup_gain;
419 sanitize_denormal(max_out);
424 if (fabsf(tau * (makeup_gain - makeup_target)) < FLT_EPSILON*makeup_gain) {
425 makeup_gain = makeup_target;
428 *(acomp->outlevel) = (max_out < 0.0056f) ? -70.f : to_dB(max_out);
429 *(acomp->inlevel) = in_peak_db;
430 acomp->makeup_gain = makeup_gain;
433 acomp->v_gainr = max_gainr;
435 if (in_peak_db > acomp->v_peakdb) {
436 acomp->v_peakdb = in_peak_db;
437 acomp->peakdb_samples = 0;
439 acomp->peakdb_samples += n_samples;
440 if ((float)acomp->peakdb_samples/acomp->srate > RESET_PEAK_AFTER_SECONDS) {
441 acomp->v_peakdb = in_peak_db;
442 acomp->peakdb_samples = 0;
443 acomp->need_expose = true;
447 const float v_lvl_in = in_peak_db;
448 const float v_lvl_out = *acomp->outlevel;
452 const float knee_lim_gr = (1.f - 1.f/ratio) * width/2.f;
454 if (acomp->v_gainr > knee_lim_gr) {
455 state_x = acomp->v_gainr / (1.f - 1.f/ratio) + thresdb;
457 state_x = sqrt ( (2.f*width*acomp->v_gainr) / (1.f-1.f/ratio) ) + thresdb - width/2.f;
460 if (fabsf (acomp->v_lvl_out - v_lvl_out) >= .1f ||
461 fabsf (acomp->v_lvl_in - v_lvl_in) >= .1f ||
462 fabsf (acomp->v_state_x - state_x) >= .1f ) {
463 // >= 0.1dB difference
464 acomp->need_expose = true;
465 acomp->v_lvl_in = v_lvl_in;
466 acomp->v_lvl_out = v_lvl_out;
467 acomp->v_state_x = state_x;
469 if (acomp->need_expose && acomp->queue_draw) {
470 acomp->need_expose = false;
471 acomp->queue_draw->queue_draw (acomp->queue_draw->handle);
477 deactivate(LV2_Handle instance)
483 cleanup(LV2_Handle instance)
486 AComp* acomp = (AComp*)instance;
487 if (acomp->display) {
488 cairo_surface_destroy (acomp->display);
497 #define MIN(A,B) ((A) < (B)) ? (A) : (B)
502 comp_curve (const AComp* self, float xg) {
503 const float knee = self->v_knee;
504 const float ratio = self->v_ratio;
505 const float thresdb = self->v_thresdb;
506 const float makeup = self->v_makeup;
508 const float width = 6.f * knee + 0.01f;
511 if (2.f * (xg - thresdb) < -width) {
513 } else if (2.f * (xg - thresdb) > width) {
514 yg = thresdb + (xg - thresdb) / ratio;
516 yg = xg + (1.f / ratio - 1.f ) * (xg - thresdb + width / 2.f) * (xg - thresdb + width / 2.f) / (2.f * width);
525 #include "dynamic_display.c"
528 render_inline_full (cairo_t* cr, const AComp* self)
530 const float w = self->w;
531 const float h = self->h;
533 const float makeup_thres = self->v_thresdb + self->v_makeup;
537 if (self->v_thresdb < 0) {
538 const float y = -.5 + floorf (h * ((makeup_thres - 10.f) / -70.f));
539 cairo_move_to (cr, 0, y);
540 cairo_line_to (cr, w, y);
544 draw_GR_bar (cr, w,h, self->v_gainr);
547 cairo_set_source_rgba (cr, .8, .8, .8, 1.0);
549 const float state_x = w * (1.f - (10.f-self->v_state_x)/70.f);
550 const float state_y = h * (comp_curve (self, self->v_state_x) - 10.f) / -70.f;
552 cairo_arc (cr, state_x, state_y, 3.f, 0.f, 2.f*M_PI);
556 cairo_set_source_rgba (cr, .8, .8, .8, 1.0);
557 cairo_move_to (cr, 0, h);
559 for (uint32_t x = 0; x < w; ++x) {
561 const float x_db = 70.f * (-1.f + x / (float)w) + 10.f;
562 const float y_db = comp_curve (self, x_db) - 10.f;
563 const float y = h * (y_db / -70.f);
564 cairo_line_to (cr, x, y);
566 cairo_stroke_preserve (cr);
568 cairo_line_to (cr, w, h);
569 cairo_close_path (cr);
572 // draw signal level & reduction/gradient
573 const float top = comp_curve (self, 0) - 10.f;
574 cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, h);
575 if (top > makeup_thres - 10.f) {
576 cairo_pattern_add_color_stop_rgba (pat, 0.0, 0.8, 0.1, 0.1, 0.5);
577 cairo_pattern_add_color_stop_rgba (pat, top / -70.f, 0.8, 0.1, 0.1, 0.5);
579 if (self->v_knee > 0) {
580 cairo_pattern_add_color_stop_rgba (pat, ((makeup_thres -10.f) / -70.f), 0.7, 0.7, 0.2, 0.5);
581 cairo_pattern_add_color_stop_rgba (pat, ((makeup_thres - self->v_knee - 10.f) / -70.f), 0.5, 0.5, 0.5, 0.5);
583 cairo_pattern_add_color_stop_rgba (pat, ((makeup_thres - 10.f)/ -70.f), 0.7, 0.7, 0.2, 0.5);
584 cairo_pattern_add_color_stop_rgba (pat, ((makeup_thres - 10.01f) / -70.f), 0.5, 0.5, 0.5, 0.5);
586 cairo_pattern_add_color_stop_rgba (pat, 1.0, 0.5, 0.5, 0.5, 0.5);
588 // maybe cut off at x-position?
589 const float x = w * (self->v_lvl_in + 60) / 70.f;
590 const float y = x + h*self->v_makeup;
591 cairo_rectangle (cr, 0, h - y, x, y);
592 if (self->v_ratio > 1.0) {
593 cairo_set_source (cr, pat);
595 cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.5);
599 cairo_pattern_destroy (pat); // TODO cache pattern
603 render_inline_only_bars (cairo_t* cr, const AComp* self)
605 draw_inline_bars (cr, self->w, self->h,
606 self->v_thresdb, self->v_ratio,
607 self->v_peakdb, self->v_gainr,
608 self->v_lvl_in, self->v_lvl_out);
611 static LV2_Inline_Display_Image_Surface *
612 render_inline (LV2_Handle instance, uint32_t w, uint32_t max_h)
614 AComp* self = (AComp*)instance;
616 uint32_t h = MIN (w, max_h);
617 if (w < 200 && !self->v_full_inline_display) {
621 if (!self->display || self->w != w || self->h != h) {
622 if (self->display) cairo_surface_destroy(self->display);
623 self->display = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
628 cairo_t* cr = cairo_create (self->display);
630 if (w >= 200 || self->v_full_inline_display) {
631 render_inline_full (cr, self);
633 render_inline_only_bars (cr, self);
638 cairo_surface_flush (self->display);
639 self->surf.width = cairo_image_surface_get_width (self->display);
640 self->surf.height = cairo_image_surface_get_height (self->display);
641 self->surf.stride = cairo_image_surface_get_stride (self->display);
642 self->surf.data = cairo_image_surface_get_data (self->display);
649 extension_data(const char* uri)
652 static const LV2_Inline_Display_Interface display = { render_inline };
653 if (!strcmp(uri, LV2_INLINEDISPLAY__interface)) {
660 static const LV2_Descriptor descriptor_mono = {
671 static const LV2_Descriptor descriptor_stereo = {
683 const LV2_Descriptor*
684 lv2_descriptor(uint32_t index)
688 return &descriptor_mono;
690 return &descriptor_stereo;