2 * Copyright (C) 2017 Johannes Mueller <github@johannes-mueller.org>
3 * based on a-comp (C) 2016 Damien Zammit <damien@zamaudio.com>
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
24 #include <cairo/cairo.h>
25 #include "ardour/lv2_extensions.h"
28 #include "lv2/lv2plug.in/ns/lv2core/lv2.h"
30 #define RESET_PEAK_AFTER_SECONDS 3
32 #define AEXP_URI "urn:ardour:a-exp"
33 #define AEXP_STEREO_URI "urn:ardour:a-exp#stereo"
36 # define M_PI 3.14159265358979323846
41 #define isfinite_local(val) (bool)_finite((double)val)
43 #define isfinite_local isfinite
47 # define FLT_EPSILON 1.192093e-07
101 LV2_Inline_Display_Image_Surface surf;
103 cairo_surface_t* display;
104 LV2_Inline_Display* queue_draw;
107 /* ports pointers are only valid during run so we'll
108 * have to cache them for the display, besides
109 * we do want to check for changes
120 uint32_t peakdb_samples;
125 instantiate(const LV2_Descriptor* descriptor,
127 const char* bundle_path,
128 const LV2_Feature* const* features)
130 AExp* aexp = (AExp*)calloc(1, sizeof(AExp));
132 if (!strcmp (descriptor->URI, AEXP_URI)) {
133 aexp->n_channels = 1;
134 } else if (!strcmp (descriptor->URI, AEXP_STEREO_URI)) {
135 aexp->n_channels = 2;
141 for (int i=0; features[i]; ++i) {
143 if (!strcmp(features[i]->URI, LV2_INLINEDISPLAY__queue_draw)) {
144 aexp->queue_draw = (LV2_Inline_Display*) features[i]->data;
151 aexp->need_expose = true;
152 aexp->v_lvl_out = -70.f;
155 return (LV2_Handle)aexp;
159 connect_port(LV2_Handle instance,
163 AExp* aexp = (AExp*)instance;
165 switch ((PortIndex)port) {
167 aexp->attack = (float*)data;
170 aexp->release = (float*)data;
173 aexp->knee = (float*)data;
176 aexp->ratio = (float*)data;
179 aexp->thresdb = (float*)data;
182 aexp->makeup = (float*)data;
185 aexp->gainr = (float*)data;
188 aexp->outlevel = (float*)data;
191 aexp->inlevel = (float*)data;
194 aexp->sidechain = (float*)data;
197 aexp->enable = (float*)data;
205 connect_mono(LV2_Handle instance,
209 AExp* aexp = (AExp*)instance;
210 connect_port (instance, port, data);
212 switch ((PortIndex)port) {
214 aexp->input0 = (float*)data;
217 aexp->sc = (float*)data;
220 aexp->output0 = (float*)data;
228 connect_stereo(LV2_Handle instance,
232 AExp* aexp = (AExp*)instance;
233 connect_port (instance, port, data);
235 switch ((PortIndex)port) {
237 aexp->input0 = (float*)data;
240 aexp->input1 = (float*)data;
243 aexp->sc = (float*)data;
246 aexp->output0 = (float*)data;
249 aexp->output1 = (float*)data;
256 // Force already-denormal float value to zero
258 sanitize_denormal(float value) {
259 if (!isnormal(value)) {
267 return (exp(gdb/20.f*log(10.f)));
272 return (20.f*log10(g));
276 activate(LV2_Handle instance)
278 AExp* aexp = (AExp*)instance;
280 *(aexp->gainr) = 160.0f;
281 *(aexp->outlevel) = -45.0f;
282 *(aexp->inlevel) = -45.0f;
285 aexp->v_peakdb = -160.f;
286 aexp->peakdb_samples = 0;
292 run(LV2_Handle instance, uint32_t n_samples)
294 AExp* aexp = (AExp*)instance;
296 const float* const ins[2] = { aexp->input0, aexp->input1 };
297 const float* const sc = aexp->sc;
298 float* const outs[2] = { aexp->output0, aexp->output1 };
300 float srate = aexp->srate;
301 float width = (6.f * *(aexp->knee)) + 0.01;
302 float attack_coeff = exp(-1000.f/(*(aexp->attack) * srate));
303 float release_coeff = exp(-1000.f/(*(aexp->release) * srate));
309 float old_gainr = *aexp->gainr;
311 int usesidechain = (*(aexp->sidechain) <= 0.f) ? 0 : 1;
317 uint32_t n_channels = aexp->n_channels;
319 float ratio = *aexp->ratio;
320 float thresdb = *aexp->thresdb;
321 float makeup = *aexp->makeup;
322 float makeup_target = from_dB(makeup);
323 float makeup_gain = aexp->makeup_gain;
325 const float tau = (1.0 - exp (-2.f * M_PI * 25.f / aexp->srate));
327 if (*aexp->enable <= 0) {
332 if (!aexp->was_disabled) {
334 aexp->was_disabled = true;
337 if (aexp->was_disabled) {
338 *aexp->gainr = 160.f;
339 aexp->was_disabled = false;
344 if (aexp->v_knee != *aexp->knee) {
345 aexp->v_knee = *aexp->knee;
346 aexp->need_expose = true;
349 if (aexp->v_ratio != ratio) {
350 aexp->v_ratio = ratio;
351 aexp->need_expose = true;
354 if (aexp->v_thresdb != thresdb) {
355 aexp->v_thresdb = thresdb;
356 aexp->need_expose = true;
359 if (aexp->v_makeup != makeup) {
360 aexp->v_makeup = makeup;
361 aexp->need_expose = true;
365 float in_peak_db = -160.f;
366 old_gainr = *aexp->gainr;
367 float max_gainr = 0.0;
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);
380 if (Lxg > in_peak_db) {
384 if (2.f*(Lxg-thresdb) < -width) {
385 Lyg = thresdb + (Lxg-thresdb) * ratio;
386 Lyg = sanitize_denormal(Lyg);
387 } else if (2.f*(Lxg-thresdb) > width) {
390 Lyg = Lxg + (1.f-ratio)*(Lxg-thresdb-width/2.f)*(Lxg-thresdb-width/2.f)/(2.f*width);
393 current_gainr = Lxg - Lyg;
395 if (current_gainr > 160.f) {
396 current_gainr = 160.f;
399 if (current_gainr > old_gainr) {
400 current_gainr = release_coeff*old_gainr + (1.f-release_coeff)*current_gainr;
401 } else if (current_gainr < old_gainr) {
402 current_gainr = attack_coeff*old_gainr + (1.f-attack_coeff)*current_gainr;
405 current_gainr = sanitize_denormal(current_gainr);
407 Lgain = from_dB(-current_gainr);
409 old_gainr = current_gainr;
411 *(aexp->gainr) = current_gainr;
412 if (current_gainr > max_gainr) {
413 max_gainr = current_gainr;
416 makeup_gain += tau * (makeup_target - makeup_gain);
418 for (uint32_t c=0; c<n_channels; ++c) {
419 float out = ins[c][i] * Lgain * makeup_gain;
424 sanitize_denormal(max_out);
429 if (fabsf(tau * (makeup_gain - makeup_target)) < FLT_EPSILON*makeup_gain) {
430 makeup_gain = makeup_target;
433 *(aexp->outlevel) = (max_out < 0.0056f) ? -45.f : to_dB(max_out);
434 *(aexp->inlevel) = in_peak_db;
435 aexp->makeup_gain = makeup_gain;
438 if (in_peak_db > aexp->v_peakdb) {
439 aexp->v_peakdb = in_peak_db;
440 aexp->peakdb_samples = 0;
442 aexp->peakdb_samples += n_samples;
443 if ((float)aexp->peakdb_samples/aexp->srate > RESET_PEAK_AFTER_SECONDS) {
444 aexp->v_peakdb = in_peak_db;
445 aexp->peakdb_samples = 0;
446 aexp->need_expose = true;
450 const float v_lvl_out = (max_out < 0.001f) ? -1600.f : to_dB(max_out);
451 const float v_lvl_in = in_peak_db;
453 if (fabsf (aexp->v_lvl_out - v_lvl_out) >= .1 ||
454 fabsf (aexp->v_lvl_in - v_lvl_in) >= .1 ||
455 fabsf (aexp->v_gainr - max_gainr) >= .1) {
456 // >= 0.1dB difference
457 aexp->need_expose = true;
458 aexp->v_lvl_in = v_lvl_in;
459 aexp->v_lvl_out = v_lvl_out;
460 aexp->v_gainr = max_gainr;
462 if (aexp->need_expose && aexp->queue_draw) {
463 aexp->need_expose = false;
464 aexp->queue_draw->queue_draw (aexp->queue_draw->handle);
471 deactivate(LV2_Handle instance)
477 cleanup(LV2_Handle instance)
480 AExp* aexp = (AExp*)instance;
482 cairo_surface_destroy (aexp->display);
491 #define MIN(A,B) ((A) < (B)) ? (A) : (B)
496 exp_curve (const AExp* self, float xg) {
497 const float knee = self->v_knee;
498 const float ratio = self->v_ratio;
499 const float thresdb = self->v_thresdb;
500 const float makeup = self->v_makeup;
502 const float width = 6.f * knee + 0.01f;
505 if (2.f * (xg - thresdb) < -width) {
506 yg = thresdb + (xg - thresdb) * ratio;
507 } else if (2.f * (xg - thresdb) > width) {
510 yg = xg + (1.f - ratio) * (xg - thresdb - width / 2.f) * (xg - thresdb - width / 2.f) / (2.f * width);
518 #include "dynamic_display.c"
521 render_inline_full (cairo_t* cr, const AExp* self)
523 const float w = self->w;
524 const float h = self->h;
526 const float makeup_thres = self->v_thresdb + self->v_makeup;
530 if (self->v_thresdb < 0) {
531 const float y = -.5 + floorf (h * ((makeup_thres - 10.f) / -70.f));
532 cairo_move_to (cr, 0, y);
533 cairo_line_to (cr, w, y);
537 draw_GR_bar (cr, w,h, self->v_gainr);
540 cairo_set_source_rgba (cr, .8, .8, .8, 1.0);
541 cairo_set_line_width(cr, 1.0);
543 const float peak_x = w * (1.f - (10.f-self->v_peakdb)/70.f);
544 const float peak_y = fminf (h * (exp_curve (self, self->v_peakdb) - 10.f) / -70.f, h);
546 cairo_arc (cr, peak_x, peak_y, 3.f, 0.f, 2.f*M_PI);
550 cairo_set_source_rgba (cr, .8, .8, .8, 1.0);
551 cairo_move_to (cr, 0, h);
553 for (uint32_t x = 0; x < w; ++x) {
555 const float x_db = 70.f * (-1.f + x / (float)w) + 10.f;
556 const float y_db = exp_curve (self, x_db) - 10.f;
557 const float y = h * (y_db / -70.f);
558 cairo_line_to (cr, x, y);
560 cairo_stroke_preserve (cr);
562 cairo_line_to (cr, w, h);
563 cairo_close_path (cr);
566 // draw signal level & reduction/gradient
567 const float top = exp_curve (self, 0) - 10.f;
568 cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, h);
569 if (top > makeup_thres - 10.f) {
570 cairo_pattern_add_color_stop_rgba (pat, 0.0, 0.8, 0.1, 0.1, 0.5);
571 cairo_pattern_add_color_stop_rgba (pat, top / -70.f, 0.8, 0.1, 0.1, 0.5);
573 if (self->v_knee > 0) {
574 cairo_pattern_add_color_stop_rgba (pat, ((makeup_thres -10.f) / -70.f), 0.7, 0.7, 0.2, 0.5);
575 cairo_pattern_add_color_stop_rgba (pat, ((makeup_thres - self->v_knee - 10.f) / -70.f), 0.5, 0.5, 0.5, 0.5);
577 cairo_pattern_add_color_stop_rgba (pat, ((makeup_thres - 10.f)/ -70.f), 0.7, 0.7, 0.2, 0.5);
578 cairo_pattern_add_color_stop_rgba (pat, ((makeup_thres - 10.01f) / -70.f), 0.5, 0.5, 0.5, 0.5);
580 cairo_pattern_add_color_stop_rgba (pat, 1.0, 0.5, 0.5, 0.5, 0.5);
582 // maybe cut off at x-position?
583 const float x = w * (self->v_lvl_in + 60) / 70.f;
584 const float y = x + h*self->v_makeup;
585 cairo_rectangle (cr, 0, h - y, x, y);
586 if (self->v_ratio > 1.0) {
587 cairo_set_source (cr, pat);
589 cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.5);
593 cairo_pattern_destroy (pat); // TODO cache pattern
597 render_inline_only_bars (cairo_t* cr, const AExp* self)
599 draw_inline_bars (cr, self->w, self->h,
600 self->v_thresdb, self->v_ratio,
601 self->v_peakdb, self->v_gainr,
602 self->v_lvl_in, self->v_lvl_out);
606 static LV2_Inline_Display_Image_Surface *
607 render_inline (LV2_Handle instance, uint32_t w, uint32_t max_h)
609 AExp* self = (AExp*)instance;
611 uint32_t h = MIN (w, max_h);
616 if (!self->display || self->w != w || self->h != h) {
617 if (self->display) cairo_surface_destroy(self->display);
618 self->display = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
623 cairo_t* cr = cairo_create (self->display);
626 render_inline_full (cr, self);
628 render_inline_only_bars (cr, self);
633 cairo_surface_flush (self->display);
634 self->surf.width = cairo_image_surface_get_width (self->display);
635 self->surf.height = cairo_image_surface_get_height (self->display);
636 self->surf.stride = cairo_image_surface_get_stride (self->display);
637 self->surf.data = cairo_image_surface_get_data (self->display);
644 extension_data(const char* uri)
647 static const LV2_Inline_Display_Interface display = { render_inline };
648 if (!strcmp(uri, LV2_INLINEDISPLAY__interface)) {
655 static const LV2_Descriptor descriptor_mono = {
666 static const LV2_Descriptor descriptor_stereo = {
678 const LV2_Descriptor*
679 lv2_descriptor(uint32_t index)
683 return &descriptor_mono;
685 return &descriptor_stereo;