1 /* fil4.lv2
2  *
3  * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2, or (at your option)
8  * any later version.
9  *
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.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #ifdef DISPLAY_INTERFACE
20 
21 #ifndef HYPOTF
22 #define HYPOTF(X,Y) (sqrtf (SQUARE(X) + SQUARE(Y)))
23 #endif
24 
25 #ifndef MIN
26 #define MIN(A,B) ((A) < (B)) ? (A) : (B)
27 #endif
28 
29 struct omega {
30 	float c1, s1, c2, s2;
31 };
32 
33 /* drawing helpers, calculate respone for given frequency */
34 static float get_filter_response (Fil4Paramsect const * const flt, struct omega const * const w) {
35 	float x = w->c2 + flt->s1() * w->c1 + flt->s2();
36 	float y = w->s2 + flt->s1() * w->s1;
37 	const float t1 = HYPOTF (x, y);
38 	x += flt->g0 () * (w->c2 - 1.f);
39 	y += flt->g0 () * w->s2;
40 	const float t2 = HYPOTF (x, y);
41 	return 20.f * log10f (t2 / t1);
42 }
43 
44 static float get_shelf_response (IIRProc const * const flt, struct omega const * const w) {
45 	const float _A  = flt->b0 + flt->b2;
46 	const float _B  = flt->b0 - flt->b2;
47 	const float _C  = 1.0     + flt->a2;
48 	const float _D  = 1.0     - flt->a2;
49 
50 	const float A = _A * w->c1 + flt->b1;
51 	const float B = _B * w->s1;
52 	const float C = _C * w->c1 + flt->a1;
53 	const float D = _D * w->s1;
54 	return 20.f * log10f (sqrtf ((SQUARE(A) + SQUARE(B)) * (SQUARE(C) + SQUARE(D))) / (SQUARE(C) + SQUARE(D)));
55 }
56 
57 static float get_highpass_response (HighPass const * const hip, const float freq) {
58 	if (!hip->en) {
59 		return 0;
60 	} else {
61 		// this is only an approx.
62 		const float wr = hip->freq / freq;
63 		float q;
64 		float r = (0.7 + 0.78 * tanh (1.82 * ((hip->q) -.8))); // RESHP
65 		if (r < 1.3) {
66 			q = 3.01 * sqrt(r / (r+2));
67 		} else {
68 			// clamp pole
69 			q = sqrt(4 - 0.09 / (r - 1.09));
70 		}
71 		return -10.f * log10f (SQUARE(1 + SQUARE(wr)) - SQUARE(q * wr));
72 	}
73 }
74 
75 static float get_lowpass_response (LowPass const * const lop, const float freq, const float rate , struct omega const * const _w) {
76 	if (!lop->en) {
77 		return 0;
78 	} else {
79 		// this is only an approx.
80 		const float w  = sin (M_PI * freq / rate);
81 		const float wc = sin (M_PI * lop->freq / rate);
82 		const float q =  sqrtf(4.f * lop->r / (1 + lop->r));
83 		float xhs = 0;
84 #ifdef LP_EXTRA_SHELF
85 		xhs = get_shelf_response (&lop->iir_hs, _w);
86 #endif
87 		return -10.f * log10f (SQUARE(1 + SQUARE(w/wc)) - SQUARE(q * w / wc)) + xhs;
88 	}
89 }
90 
91 static float freq_at_x (const int x, const float w) {
92 	return 20.f * powf (1000.f, x / w);
93 }
94 
95 static float x_at_freq (const float f, const float w) {
96 	return rintf (w * logf (f / 20.0) / logf (1000.0));
97 }
98 
99 static LV2_Inline_Display_Image_Surface *
100 fil4_render(LV2_Handle instance, uint32_t w, uint32_t max_h)
101 {
102 #ifdef WITH_SIGNATURE
103 	if (!is_licensed (instance)) { return NULL; }
104 #endif
105 	uint32_t h = MIN (1 | (uint32_t)ceilf (w * 9.f / 16.f), max_h);
106 
107 	Fil4* self = (Fil4*)instance;
108 	if (!self->display || self->w != w || self->h != h) {
109 		if (self->display) cairo_surface_destroy(self->display);
110 		self->display = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
111 		self->w = w;
112 		self->h = h;
113 	}
114 	cairo_t* cr = cairo_create (self->display);
115 	cairo_rectangle (cr, 0, 0, w, h);
116 	if (self->enabled) {
117 		cairo_set_source_rgba (cr, .2, .2, .2, 1.0);
118 	} else {
119 		cairo_set_source_rgba (cr, .1, .1, .1, 1.0);
120 	}
121 	cairo_fill (cr);
122 
123 	const float yr = (h - 2.f) / (2.f * 20); // +/- 20dB
124 	const float ym = rintf ((h - 1.f) * .5f) + .5;
125 	const float xw = w - 1;
126 
127 	const float a = self->enabled ? 1.0 : .2;
128 	const float ny = x_at_freq (.5 * self->rate, xw);
129 
130 	/* zero line */
131 	cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
132 	cairo_set_line_width(cr, 1.0);
133 	cairo_set_source_rgba (cr, .6, .6, .6, a);
134 	cairo_move_to (cr, 1,     ym);
135 	cairo_line_to (cr, w - 1, ym);
136 	cairo_stroke(cr);
137 
138 #define X_GRID(FREQ) { \
139 	const float xx = .5 + x_at_freq (FREQ, xw); \
140 	cairo_move_to (cr, xx, 0); \
141 	cairo_line_to (cr, xx, h); \
142 	cairo_stroke (cr); \
143 }
144 
145 #define Y_GRID(dB) { \
146 	const float yy = rintf (yr * dB); \
147 	cairo_move_to (cr, 0, ym - yy); \
148 	cairo_line_to (cr, w, ym - yy); \
149 	cairo_stroke (cr); \
150 	cairo_move_to (cr, 0, ym + yy); \
151 	cairo_line_to (cr, w, ym + yy); \
152 	cairo_stroke (cr); \
153 }
154 
155 	const double dash2[] = {1, 3};
156 	cairo_save (cr);
157 	cairo_set_dash(cr, dash2, 2, 2);
158 	cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.5 * a);
159 	X_GRID (100);
160 	X_GRID (1000);
161 	X_GRID (10000);
162 	Y_GRID (6);
163 	Y_GRID (12);
164 	Y_GRID (18);
165 	cairo_restore (cr);
166 
167 	FilterChannel const * const fc = &self->fc[0];
168 
169 	if (ny < xw) {
170 		cairo_rectangle (cr, 0, 0, ny, h);
171 		cairo_clip (cr);
172 	}
173 
174 	for (uint32_t i = 0; i < xw && i < ny; ++i) {
175 		const float freq = freq_at_x (i, xw);
176 		const float w = 2.f * M_PI * freq / self->rate;
177 		struct omega _w;
178 		_w.c1 = cosf (w);
179 		_w.s1 = sinf (w);
180 		_w.c2 = cosf (2.f * w);
181 		_w.s2 = sinf (2.f * w);
182 
183 		float y = 0;
184 		for (int j = 0; j < NSECT; ++j) {
185 			y += yr * get_filter_response (&fc->_sect[j], &_w);
186 		}
187 		y += yr * get_shelf_response (&fc->iir_lowshelf, &_w);
188 		y += yr * get_shelf_response (&fc->iir_highshelf, &_w);
189 
190 		y += yr * get_highpass_response (&fc->hip, freq);
191 		y += yr * get_lowpass_response (&fc->lop, freq, self->rate, &_w);
192 
193 		if (i == 0) {
194 			cairo_move_to (cr, 0.5 + i, ym - y);
195 		} else {
196 			cairo_line_to (cr, 0.5 + i, ym - y);
197 		}
198 	}
199 
200 	cairo_set_source_rgba (cr, .8, .8, .8, a);
201 	cairo_stroke_preserve(cr);
202 	cairo_line_to (cr, w, ym);
203 	cairo_line_to (cr, 0, ym);
204 	cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.5 * a);
205 	cairo_fill (cr);
206 
207 	cairo_destroy (cr);
208 	cairo_surface_flush (self->display);
209 	self->surf.width = cairo_image_surface_get_width (self->display);
210 	self->surf.height = cairo_image_surface_get_height (self->display);
211 	self->surf.stride = cairo_image_surface_get_stride (self->display);
212 	self->surf.data = cairo_image_surface_get_data  (self->display);
213 
214 	return &self->surf;
215 }
216 #endif
217