a-EQ: Revert one of the previous changes
[ardour.git] / libs / plugins / a-eq.lv2 / a-eq.c
index 30265d7955989f2b6376a41c7ce45f17a0b32fe8..b0851f2120f73bc3d47829414c19973f3f310ab4 100644 (file)
 #include <stdbool.h>
 #include <stdio.h>
 
+#ifdef COMPILER_MSVC
+#include <float.h>
+#define isfinite_local(val) (bool)_finite((double)val)
+#else
+#define isfinite_local isfinite
+#endif
+
 #include "lv2/lv2plug.in/ns/lv2core/lv2.h"
 
 #ifdef LV2_EXTENDED
 
 #define AEQ_URI        "urn:ardour:a-eq"
 #define BANDS  6
-
 #ifndef MIN
 #define MIN(A,B) ((A) < (B)) ? (A) : (B)
 #endif
 
 typedef enum {
-       AEQ_SHELFTOGL = 0,
-       AEQ_FREQL,
+       AEQ_FREQL = 0,
        AEQ_GAINL,
        AEQ_FREQ1,
        AEQ_GAIN1,
@@ -53,7 +58,6 @@ typedef enum {
        AEQ_FREQ4,
        AEQ_GAIN4,
        AEQ_BW4,
-       AEQ_SHELFTOGH,
        AEQ_FREQH,
        AEQ_GAINH,
        AEQ_MASTER,
@@ -63,18 +67,24 @@ typedef enum {
        AEQ_FILTOG3,
        AEQ_FILTOG4,
        AEQ_FILTOGH,
+       AEQ_ENABLE,
        AEQ_INPUT,
        AEQ_OUTPUT,
 } PortIndex;
 
-static inline float
-to_dB(float g) {
-       return (20.f*log10(g));
+static inline double
+to_dB(double g) {
+       return (20.0*log10(g));
 }
 
-static inline float
-from_dB(float gdb) {
-       return (exp(gdb/20.f*log(10.f)));
+static inline double
+from_dB(double gdb) {
+       return (exp(gdb/20.0*log(10.0)));
+}
+
+static inline bool
+is_eq(float a, float b, float small) {
+       return (fabsf(a - b) < small);
 }
 
 struct linear_svf {
@@ -89,16 +99,23 @@ static void linear_svf_reset(struct linear_svf *self)
        self->s[0] = self->s[1] = 0.0;
 }
 
+static void linear_svf_protect(struct linear_svf *self)
+{
+       if (!isfinite_local (self->s[0]) || !isfinite_local (self->s[1])) {
+               linear_svf_reset (self);
+       }
+}
+
 typedef struct {
-       float* shelftogl;
-       float* shelftogh;
        float* f0[BANDS];
        float* g[BANDS];
        float* bw[BANDS];
        float* filtog[BANDS];
        float* master;
+       float* enable;
 
        float srate;
+       float tau;
 
        float* input;
        float* output;
@@ -107,9 +124,6 @@ typedef struct {
        float v_g[BANDS];
        float v_bw[BANDS];
        float v_f0[BANDS];
-       float v_filtog[BANDS];
-       float v_shelftogl;
-       float v_shelftogh;
        float v_master;
 
        bool need_expose;
@@ -128,9 +142,10 @@ instantiate(const LV2_Descriptor* descriptor,
             const char* bundle_path,
             const LV2_Feature* const* features)
 {
-       Aeq* aeq = (Aeq*)malloc(sizeof(Aeq));
+       Aeq* aeq = (Aeq*)calloc(1, sizeof(Aeq));
        aeq->srate = rate;
-       
+       aeq->tau = 1.0 - expf (-2.f * M_PI * 64.f * 25.f / aeq->srate); // 25Hz time constant @ 64fpp
+
 #ifdef LV2_EXTENDED
        for (int i=0; features[i]; ++i) {
                if (!strcmp(features[i]->URI, LV2_INLINEDISPLAY__queue_draw)) {
@@ -143,7 +158,9 @@ instantiate(const LV2_Descriptor* descriptor,
                linear_svf_reset(&aeq->v_filter[i]);
 
        aeq->need_expose = true;
+#ifdef LV2_EXTENDED
        aeq->display = NULL;
+#endif
 
        return (LV2_Handle)aeq;
 }
@@ -156,8 +173,8 @@ connect_port(LV2_Handle instance,
        Aeq* aeq = (Aeq*)instance;
 
        switch ((PortIndex)port) {
-       case AEQ_SHELFTOGL:
-               aeq->shelftogl = (float*)data;
+       case AEQ_ENABLE:
+               aeq->enable = (float*)data;
                break;
        case AEQ_FREQL:
                aeq->f0[0] = (float*)data;
@@ -201,9 +218,6 @@ connect_port(LV2_Handle instance,
        case AEQ_BW4:
                aeq->bw[4] = (float*)data;
                break;
-       case AEQ_SHELFTOGH:
-               aeq->shelftogh = (float*)data;
-               break;
        case AEQ_FREQH:
                aeq->f0[5] = (float*)data;
                break;
@@ -253,46 +267,10 @@ activate(LV2_Handle instance)
 // SVF filters
 // http://www.cytomic.com/files/dsp/SvfLinearTrapOptimised2.pdf
 
-static void linear_svf_set_hp(struct linear_svf *self, float sample_rate, float cutoff, float resonance)
-{
-       double f0 = (double)cutoff;
-       double q = (double)resonance;
-       double sr = (double)sample_rate;
-
-       self->g = tan(M_PI * (f0 / sr));
-       self->k = 1.0 / q;
-
-       self->a[0] = 1.0 / (1.0 + self->g * (self->g + self->k));
-       self->a[1] = self->g * self->a[0];
-       self->a[2] = self->g * self->a[1];
-
-       self->m[0] = 1.0;
-       self->m[1] = -self->k;
-       self->m[2] = -1.0;
-}
-
-static void linear_svf_set_lp(struct linear_svf *self, float sample_rate, float cutoff, float resonance)
-{
-       double f0 = (double)cutoff;
-       double q = (double)resonance;
-       double sr = (double)sample_rate;
-
-       self->g = tan(M_PI * (f0 / sr));
-       self->k = 1.0 / q;
-
-       self->a[0] = 1.0 / (1.0 + self->g * (self->g + self->k));
-       self->a[1] = self->g * self->a[0];
-       self->a[2] = self->g * self->a[1];
-
-       self->m[0] = 0.0;
-       self->m[1] = 0.0;
-       self->m[2] = 1.0;
-}
-
 static void linear_svf_set_peq(struct linear_svf *self, float gdb, float sample_rate, float cutoff, float bandwidth)
 {
        double f0 = (double)cutoff;
-       double q = (double)pow(2.0, 1.0 / bandwidth) / (pow(2.0, bandwidth) - 1.0);
+       double q = (double)pow(2.0, 0.5 * bandwidth) / (pow(2.0, bandwidth) - 1.0);
        double sr = (double)sample_rate;
        double A = pow(10.0, gdb/40.0);
 
@@ -366,6 +344,25 @@ static float run_linear_svf(struct linear_svf *self, float in)
        return (float)out;
 }
 
+static void set_params(LV2_Handle instance, int band) {
+       Aeq* aeq = (Aeq*)instance;
+
+       switch (band) {
+       case 0:
+               linear_svf_set_lowshelf(&aeq->v_filter[0], aeq->v_g[0], aeq->srate, aeq->v_f0[0], 0.7071068);
+               break;
+       case 1:
+       case 2:
+       case 3:
+       case 4:
+               linear_svf_set_peq(&aeq->v_filter[band], aeq->v_g[band], aeq->srate, aeq->v_f0[band], aeq->v_bw[band]);
+               break;
+       case 5:
+               linear_svf_set_highshelf(&aeq->v_filter[5], aeq->v_g[5], aeq->srate, aeq->v_f0[5], 0.7071068);
+               break;
+       }
+}
+
 static void
 run(LV2_Handle instance, uint32_t n_samples)
 {
@@ -374,65 +371,75 @@ run(LV2_Handle instance, uint32_t n_samples)
        const float* const input = aeq->input;
        float* const output = aeq->output;
 
-       float srate = aeq->srate;
-       float in0, out;
-       uint32_t i, j;
+       const float tau = aeq->tau;
+       uint32_t offset = 0;
 
-       if (*(aeq->shelftogl) > 0.5) {
-               linear_svf_set_lowshelf(&aeq->v_filter[0], *(aeq->g[0]), srate, *(aeq->f0[0]), 0.7071068);
-       } else {
-               linear_svf_set_hp(&aeq->v_filter[0], srate, *(aeq->f0[0]), 0.7071068);
-       }
-       linear_svf_set_peq(&aeq->v_filter[1], *(aeq->g[1]), srate, *(aeq->f0[1]), *(aeq->bw[1]));
-       linear_svf_set_peq(&aeq->v_filter[2], *(aeq->g[2]), srate, *(aeq->f0[2]), *(aeq->bw[2]));
-       linear_svf_set_peq(&aeq->v_filter[3], *(aeq->g[3]), srate, *(aeq->f0[3]), *(aeq->bw[3]));
-       linear_svf_set_peq(&aeq->v_filter[4], *(aeq->g[4]), srate, *(aeq->f0[4]), *(aeq->bw[4]));
-
-       if (*(aeq->shelftogh) > 0.5) {
-               linear_svf_set_highshelf(&aeq->v_filter[5], *(aeq->g[5]), srate, *(aeq->f0[5]), 0.7071068);
-       } else {
-               linear_svf_set_lp(&aeq->v_filter[5], srate, *(aeq->f0[5]), 0.7071068);
-       }
+       const float target_gain = *aeq->enable <= 0 ? 0 : *aeq->master; // dB
 
-       for (i = 0; i < n_samples; i++) {
-               in0 = input[i];
-               out = in0;
-               for (j = 0; j < BANDS; j++) {
-                       if (*(aeq->filtog[j]) > 0.5)
-                               out = run_linear_svf(&aeq->v_filter[j], out);
-               }
-               output[i] = out * from_dB(*(aeq->master));
-       }
+       while (n_samples > 0) {
+               uint32_t block = n_samples;
+               bool any_changed = false;
 
-       for (i = 0; i < BANDS; i++) {
-               if (aeq->v_f0[i] != *(aeq->f0[i])) {
-                       aeq->v_f0[i] = *(aeq->f0[i]);
-                       aeq->need_expose = true;
-               }
-               if (aeq->v_g[i] != *(aeq->g[i])) {
-                       aeq->v_g[i] = *(aeq->g[i]);
-                       aeq->need_expose = true;
-               }
-               if (i != 0 && i != 5 && aeq->v_bw[i] != *(aeq->bw[i])) {
-                       aeq->v_bw[i] = *(aeq->bw[i]);
-                       aeq->need_expose = true;
-               }
-               if (aeq->v_filtog[i] != *(aeq->filtog[i])) {
-                       aeq->v_filtog[i] = *(aeq->filtog[i]);
-                       aeq->need_expose = true;
+               if (!is_eq(aeq->v_master, target_gain, 0.1)) {
+                       aeq->v_master += tau * (target_gain - aeq->v_master);
+                       any_changed = true;
+               } else {
+                       aeq->v_master = target_gain;
                }
-               if (aeq->v_shelftogl != *(aeq->shelftogl)) {
-                       aeq->v_shelftogl = *(aeq->shelftogl);
-                       aeq->need_expose = true;
+
+               for (int i = 0; i < BANDS; ++i) {
+                       bool changed = false;
+
+                       if (!is_eq(aeq->v_f0[i], *aeq->f0[i], 0.1)) {
+                               aeq->v_f0[i] += tau * (*aeq->f0[i] - aeq->v_f0[i]);
+                               changed = true;
+                       }
+
+                       if (*aeq->filtog[i] <= 0 || *aeq->enable <= 0) {
+                               if (!is_eq(aeq->v_g[i], 0.f, 0.05)) {
+                                       aeq->v_g[i] += tau * (0.0 - aeq->v_g[i]);
+                                       changed = true;
+                               }
+                       } else {
+                               if (!is_eq(aeq->v_g[i], *aeq->g[i], 0.05)) {
+                                       aeq->v_g[i] += tau * (*aeq->g[i] - aeq->v_g[i]);
+                                       changed = true;
+                               }
+                       }
+
+                       if (i != 0 && i != 5) {
+                               if (!is_eq(aeq->v_bw[i], *aeq->bw[i], 0.001)) {
+                                       aeq->v_bw[i] += tau * (*aeq->bw[i] - aeq->v_bw[i]);
+                                       changed = true;
+                               }
+                       }
+
+                       if (changed) {
+                               set_params(aeq, i);
+                               any_changed = true;
+                       }
                }
-               if (aeq->v_shelftogh != *(aeq->shelftogh)) {
-                       aeq->v_shelftogh = *(aeq->shelftogh);
+
+               if (any_changed) {
                        aeq->need_expose = true;
+                       block = MIN (64, n_samples);
                }
-               if (aeq->v_master != *(aeq->master)) {
-                       aeq->v_master = *(aeq->master);
-                       aeq->need_expose = true;
+
+               for (uint32_t i = 0; i < block; ++i) {
+                       float in0, out;
+                       in0 = input[i + offset];
+                       out = in0;
+                       for (uint32_t j = 0; j < BANDS; j++) {
+                               out = run_linear_svf(&aeq->v_filter[j], out);
+                       }
+                       output[i + offset] = out * from_dB(aeq->v_master);
                }
+               n_samples -= block;
+               offset += block;
+       }
+
+       for (uint32_t j = 0; j < BANDS; j++) {
+               linear_svf_protect(&aeq->v_filter[j]);
        }
 
 #ifdef LV2_EXTENDED
@@ -443,110 +450,91 @@ run(LV2_Handle instance, uint32_t n_samples)
 #endif
 }
 
+static double
+calc_peq(Aeq* self, int i, double omega) {
+       double complex H = 0.0;
+       double complex z = cexp(I * omega);
+       double complex zz = cexp(2. * I * omega);
+       double complex zm = z - 1.0;
+       double complex zp = z + 1.0;
+       double complex zzm = zz - 1.0;
+
+       double A = pow(10.0, self->v_g[i]/40.0);
+       double g = self->v_filter[i].g;
+       double k = self->v_filter[i].k * A;
+       double m1 = k * (A * A - 1.0) / A;
+
+       H = (g*k*zzm + A*(g*zp*(m1*zm) + (zm*zm + g*g*zp*zp))) / (g*k*zzm + A*(zm*zm + g*g*zp*zp));
+       return cabs(H);
+}
+
+static double
+calc_lowshelf(Aeq* self, double omega) {
+       double complex H = 0.0;
+       double complex z = cexp(I * omega);
+       double complex zz = cexp(2. * I * omega);
+       double complex zm = z - 1.0;
+       double complex zp = z + 1.0;
+       double complex zzm = zz - 1.0;
+
+       double A = pow(10.0, self->v_g[0]/40.0);
+       double g = self->v_filter[0].g;
+       double k = self->v_filter[0].k;
+       double m0 = self->v_filter[0].m[0];
+       double m1 = self->v_filter[0].m[1];
+       double m2 = self->v_filter[0].m[2];
+
+       H = (A*m0*zm*zm + g*g*(m0+m2)*zp*zp + sqrt(A)*g*(k*m0+m1) * zzm) / (A*zm*zm + g*g*zp*zp + sqrt(A)*g*k*zzm);
+       return cabs(H);
+}
+
+static double
+calc_highshelf(Aeq* self, double omega) {
+       double complex H = 0.0;
+       double complex z = cexp(I * omega);
+       double complex zz = cexp(2. * I * omega);
+       double complex zm = z - 1.0;
+       double complex zp = z + 1.0;
+       double complex zzm = zz - 1.0;
+
+       double A = pow(10.0, self->v_g[5]/40.0);
+       double g = self->v_filter[5].g;
+       double k = self->v_filter[5].k;
+       double m0 = self->v_filter[5].m[0];
+       double m1 = self->v_filter[5].m[1];
+       double m2 = self->v_filter[5].m[2];
+
+       H = ( sqrt(A) * g * zp * (m1 * zm + sqrt(A)*g*m2*zp) + m0 * ( zm*zm + A*g*g*zp*zp + sqrt(A)*g*k*zzm)) / (zm*zm + A*g*g*zp*zp + sqrt(A)*g*k*zzm);
+       return cabs(H);
+}
 
 #ifdef LV2_EXTENDED
 static float
 eq_curve (Aeq* self, float f) {
-       float SR = self->srate;
-       double complex H = 1.0;
-       double theta = f * 2. * M_PI / SR;
-       double complex z = cexp(I * theta);
-       double A;
-       double m0, m1, m2, g, k;
-       int j = 0;
-
-       // low
-       if (self->v_filtog[0]) {
-               A = pow(10.0, self->v_g[0]/40.0);
-               m0 = self->v_filter[0].m[0];
-               m1 = self->v_filter[0].m[1];
-               m2 = self->v_filter[0].m[2];
-               g = self->v_filter[0].g;
-               k = self->v_filter[0].k;
-               if (self->v_shelftogl) {
-                       // lowshelf
-                       H *= (A*m0*(z-1.0)*(z-1.0) + g*g*(m0+m2)*(1.0+z)*(1.0+z) + sqrt(A)*g*(k*m0+m1) * (z*z-1.0)) / (A*(z-1.0)*(z-1.0) + g*g*(1.0+z)*(1.0+z) + sqrt(A)*g*k*(z*z-1.0));
-               } else {
-                       // hp:
-                       H *= ((z-1.0)*(z-1.0)) / ((z-1.0)*(z-1.0) + g*g*(1.0+z)*(1.0+z) + g*k*(z*z-1.0));
-               }
-               j++;
-       }
-
-       // peq1:
-       if (self->v_filtog[1]) {
-               A = pow(10.0, self->v_g[1]/40.0);
-               m0 = self->v_filter[1].m[0];
-               m1 = self->v_filter[1].m[1];
-               m2 = self->v_filter[1].m[2];
-               g = self->v_filter[1].g;
-               k = self->v_filter[1].k;
-               H *= (g*k*m0*(z*z-1.0) + A*(g*(1.0+z)*(m1*(z-1.0) + g*m2*(1.0+z)) + m0*((z-1.0)*(z-1.0) + g*g*(1.0+z)*(1.0+z)))) / (g*k*(z*z-1.0) + A*((z-1.0)*(z-1.0) + g*g*(1.0+z)*(1.0+z)));
-               j++;
-       }
-
-       // peq2:
-       if (self->v_filtog[2]) {
-               A = pow(10.0, self->v_g[2]/40.0);
-               m0 = self->v_filter[2].m[0];
-               m1 = self->v_filter[2].m[1];
-               m2 = self->v_filter[2].m[2];
-               g = self->v_filter[2].g;
-               k = self->v_filter[2].k;
-               H *= (g*k*m0*(z*z-1.0) + A*(g*(1.0+z)*(m1*(z-1.0) + g*m2*(1.0+z)) + m0*((z-1.0)*(z-1.0) + g*g*(1.0+z)*(1.0+z)))) / (g*k*(z*z-1.0) + A*((z-1.0)*(z-1.0) + g*g*(1.0+z)*(1.0+z)));
-               j++;
-       }
+       double response = 1.0;
+       double SR = (double)self->srate;
+       double omega = f * 2. * M_PI / SR;
 
-       // peq3:
-       if (self->v_filtog[3]) {
-               A = pow(10.0, self->v_g[3]/40.0);
-               m0 = self->v_filter[3].m[0];
-               m1 = self->v_filter[3].m[1];
-               m2 = self->v_filter[3].m[2];
-               g = self->v_filter[3].g;
-               k = self->v_filter[3].k;
-               H *= (g*k*m0*(z*z-1.0) + A*(g*(1.0+z)*(m1*(z-1.0) + g*m2*(1.0+z)) + m0*((z-1.0)*(z-1.0) + g*g*(1.0+z)*(1.0+z)))) / (g*k*(z*z-1.0) + A*((z-1.0)*(z-1.0) + g*g*(1.0+z)*(1.0+z)));
-               j++;
-       }
+       // lowshelf
+       response *= calc_lowshelf(self, omega);
 
-       // peq4:
-       if (self->v_filtog[4]) {
-               A = pow(10.0, self->v_g[4]/40.0);
-               m0 = self->v_filter[4].m[0];
-               m1 = self->v_filter[4].m[1];
-               m2 = self->v_filter[4].m[2];
-               g = self->v_filter[4].g;
-               k = self->v_filter[4].k;
-               H *= (g*k*m0*(z*z-1.0) + A*(g*(1.0+z)*(m1*(z-1.0) + g*m2*(1.0+z)) + m0*((z-1.0)*(z-1.0) + g*g*(1.0+z)*(1.0+z)))) / (g*k*(z*z-1.0) + A*((z-1.0)*(z-1.0) + g*g*(1.0+z)*(1.0+z)));
-               j++;
-       }
+       // peq 1 - 4:
+       response *= calc_peq(self, 1, omega);
+       response *= calc_peq(self, 2, omega);
+       response *= calc_peq(self, 3, omega);
+       response *= calc_peq(self, 4, omega);
 
-       // high
-       if (self->v_filtog[5]) {
-               A = pow(10.0, self->v_g[5]/40.0);
-               m0 = self->v_filter[5].m[0];
-               m1 = self->v_filter[5].m[1];
-               m2 = self->v_filter[5].m[2];
-               g = self->v_filter[5].g;
-               k = self->v_filter[5].k;
-               if (self->v_shelftogh) {
-                       // highshelf:
-                       H *= ( sqrt(A) * g * (1.0 + z) * (m1 * (z - 1.0) + sqrt(A)*g*m2*(1.0+z)) + m0 * ( (z-1.0)*(z-1.0) + A*g*g*(1.0+z)*(1.0+z) + sqrt(A)*g*k*(z*z-1.0))) / ((z-1.0)*(z-1.0) + A*g*g*(1.0+z)*(1.0+z) + sqrt(A)*g*k*(z*z-1.0));
-               } else {
-                       // lp:
-                       H *= (g*g*(1.0+z)*(1.0+z)) / ((z-1.0)*(z-1.0) + g*g*(1.0+z)*(1.0+z) + g*k*(z*z-1.0));
-               }
-               j++;
-       }
+       // highshelf:
+       response *= calc_highshelf(self, omega);
 
-       return cabs(H);
+       return (float)response;
 }
 
 static LV2_Inline_Display_Image_Surface *
 render_inline (LV2_Handle instance, uint32_t w, uint32_t max_h)
 {
        Aeq* self = (Aeq*)instance;
-       uint32_t h = MIN (w * 9 / 16, max_h);
+       uint32_t h = MIN (1 | (uint32_t)ceilf (w * 9.f / 16.f), max_h);
 
        if (!self->display || self->w != w || self->h != h) {
                if (self->display) cairo_surface_destroy(self->display);
@@ -564,19 +552,30 @@ render_inline (LV2_Handle instance, uint32_t w, uint32_t max_h)
 
        cairo_set_line_width(cr, 1.0);
 
-       // draw grid 5dB steps
-       const double dash2[] = {1, 3};
+       // prepare grid drawing
        cairo_save (cr);
-       cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
+       const double dash2[] = {1, 3};
+       //cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
        cairo_set_dash(cr, dash2, 2, 2);
        cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.5);
 
-       for (uint32_t d = 1; d < 8; ++d) {
-               const float y = -.5 + floorf (h * (d * 5.f / 40.f));
+       // draw x-grid 6dB steps
+       for (int32_t d = -18; d <= 18; d+=6) {
+               float y = (float)h * (d / 40.0 + 0.5);
+               y = rint (y) - .5;
                cairo_move_to (cr, 0, y);
                cairo_line_to (cr, w, y);
                cairo_stroke (cr);
        }
+       // draw y-axis grid 100, 1k, 10K
+       for (int32_t f = 100; f <= 10000; f *= 10) {
+               float x = w * log10 (f / 20.0) / log10 (1000.0);
+               x = rint (x) - .5;
+               cairo_move_to (cr, x, 0);
+               cairo_line_to (cr, x, h);
+               cairo_stroke (cr);
+       }
+
        cairo_restore (cr);
 
 
@@ -588,9 +587,8 @@ render_inline (LV2_Handle instance, uint32_t w, uint32_t max_h)
                // plot 20..20kHz +-20dB
                const float x_hz = 20.f * powf (1000.f, (float)x / (float)w);
                const float y_db = to_dB(eq_curve(self, x_hz)) + self->v_master;
-               const float y = h * -y_db / 40.0 + h / 2;
+               const float y = (float)h * (-y_db / 40.0 + 0.5);
                cairo_line_to (cr, x, y);
-               //printf("(hz,H,db)=(%f, %f, %f)\n", x_hz, from_dB(y_db), y_db);
        }
        cairo_stroke_preserve (cr);