1 /*
2  * ZamPhono production/reproduction filters
3  * Copyright (C) 2016  Damien Zammit <damien@zamaudio.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of
8  * the License, or (at your option) 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 
16 #include "ZamPhonoPlugin.hpp"
17 
18 START_NAMESPACE_DISTRHO
19 
20 // -----------------------------------------------------------------------
21 
ZamPhonoPlugin()22 ZamPhonoPlugin::ZamPhonoPlugin()
23     : Plugin(paramCount, 1, 0)
24 {
25     // set default values
26     loadProgram(0);
27 }
28 
29 // -----------------------------------------------------------------------
30 // Init
31 
initParameter(uint32_t index,Parameter & parameter)32 void ZamPhonoPlugin::initParameter(uint32_t index, Parameter& parameter)
33 {
34     switch (index)
35     {
36     case paramToggle:
37         parameter.hints      = kParameterIsAutomable | kParameterIsBoolean;
38         parameter.name       = "Reproduction/Production";
39         parameter.symbol     = "inv";
40         parameter.unit       = " ";
41         parameter.ranges.def = 0.0f;
42         parameter.ranges.min = 0.0f;
43         parameter.ranges.max = 1.0f;
44         break;
45     case paramType:
46         parameter.hints      = kParameterIsAutomable | kParameterIsInteger;
47         parameter.name       = "Phono Filter Type";
48         parameter.symbol     = "type";
49         parameter.unit       = " ";
50         parameter.ranges.def = 3.0f;
51         parameter.ranges.min = 0.0f;
52         parameter.ranges.max = 4.0f;
53         break;
54     }
55 }
56 
initProgramName(uint32_t index,String & programName)57 void ZamPhonoPlugin::initProgramName(uint32_t index, String& programName)
58 {
59 	switch(index) {
60 	case 0:
61 		programName = "RIAA (Playback)";
62 		break;
63 	}
64 }
65 
loadProgram(uint32_t index)66 void ZamPhonoPlugin::loadProgram(uint32_t index)
67 {
68 	switch(index) {
69 	default:
70 	case 0:
71 		type = 3.0;
72 		inv = 0.0;
73 		break;
74 	}
75 
76 	activate();
77 }
78 
79 // -----------------------------------------------------------------------
80 // Internal data
81 
getParameterValue(uint32_t index) const82 float ZamPhonoPlugin::getParameterValue(uint32_t index) const
83 {
84     switch (index)
85     {
86     case paramToggle:
87         return inv;
88         break;
89     case paramType:
90         return type;
91         break;
92     default:
93         return 0.0f;
94     }
95 }
96 
setParameterValue(uint32_t index,float value)97 void ZamPhonoPlugin::setParameterValue(uint32_t index, float value)
98 {
99     switch (index)
100     {
101     case paramToggle:
102         inv = value;
103         break;
104     case paramType:
105         type = value;
106         break;
107     }
108 }
109 
110 // -----------------------------------------------------------------------
111 // Process
112 
brickwall(float fc,float srate)113 void ZamPhonoPlugin::brickwall(float fc, float srate)
114 {
115 	float w0, alpha, cw, sw, q;
116 	q = 0.707;
117 	w0 = (2. * M_PI * fc / srate);
118 	sw = sin(w0);
119 	cw = cos(w0);
120 	alpha = sw / (2. * q);
121 
122 	A0 = 1. + alpha;
123 	A1 = -2. * cw;
124 	A2 = 1. - alpha;
125 	B0 = (1. - cw) / 2.;
126 	B1 = (1. - cw);
127 	B2 = B0;
128 }
129 
clearbrickwall(void)130 void ZamPhonoPlugin::clearbrickwall(void)
131 {
132 	state[0] = state[1] = state[2] = state[3] = 0.0;
133 }
134 
activate()135 void ZamPhonoPlugin::activate()
136 {
137 	float srate = getSampleRate();
138 
139 	typeold = -1.f;
140 	invold = -1.f;
141 
142 	zn1 = zn2 = zd1 = zd2 = 0.0;
143 	clearbrickwall();
144 	brickwall(std::min(0.45 * srate, 21000.), srate);
145 }
146 
run_brickwall(double in)147 double ZamPhonoPlugin::run_brickwall(double in)
148 {
149 	double out;
150 	in = sanitize_denormal(in);
151 
152 	out = B0/A0*in + B1/A0*state[0] + B2/A0*state[1]
153 			-A1/A0*state[2] - A2/A0*state[3] + 1e-20;
154 
155 	state[1] = state[0];
156 	state[0] = in;
157 	state[3] = state[2];
158 	state[2] = sanitize_denormal(out);
159 	return state[2];
160 }
161 
emphasis(float srate)162 void ZamPhonoPlugin::emphasis(float srate)
163 {
164 	float t,i,j,k,g,tau1,tau2,tau3,freq;
165 
166 	switch((int)type) {
167 	case 0: //"Columbia"
168 		i = 100.f;
169 		j = 500.f;
170 		k = 1590.f;
171 		break;
172 	case 1: //"EMI"
173 		i = 70.f;
174 		j = 500.f;
175 		k = 2500.f;
176 		break;
177 	case 2: //"BSI(78rpm)"
178 		i = 50.f;
179 		j = 353.f;
180 		k = 3180.f;
181 		break;
182 	default:
183 	case 3: //"RIAA"
184 		tau1 = 0.003180f;
185 		tau2 = 0.000318f;
186 		tau3 = 0.000075f;
187 		i = 1.f / (2.f * M_PI * tau1);
188 		j = 1.f / (2.f * M_PI * tau2);
189 		k = 1.f / (2.f * M_PI * tau3);
190 		break;
191 	case 4: //"CD Emphasis"
192 		tau1 = 0.000050f;
193 		tau2 = 0.000015f;
194 		tau3 = 0.0000001f;// 1.6MHz out of audible range for null impact
195 		i = 1.f / (2.f * M_PI * tau1);
196 		j = 1.f / (2.f * M_PI * tau2);
197 		k = 1.f / (2.f * M_PI * tau3);
198 		break;
199 	}
200 
201 	t = 1.f / srate;
202 	g = 1.0;
203 
204 	i *= 2.f * M_PI;
205 	j *= 2.f * M_PI;
206 	k *= 2.f * M_PI;
207 
208 	if (inv < 0.5) {
209 		//Reproduction
210 		g = 1.f / (4.f+2.f*i*t+2.f*k*t+i*k*t*t);
211 		b0 = (2.f*t+j*t*t)*g;
212 		b1 = (2.f*j*t*t)*g;
213 		b2 = (-2.f*t+j*t*t)*g;
214 		a1 = (-8.f+2.f*i*k*t*t)*g;
215 		a2 = (4.f-2.f*i*t-2.f*k*t+i*k*t*t)*g;
216 	} else {
217 		//Production
218 		g = 1.f / (2.f*t+j*t*t);
219 		b0 = (4.f+2.f*i*t+2.f*k*t+i*k*t*t)*g;
220 		b1 = (-8.f+2.f*i*k*t*t)*g;
221 		b2 = (4.f-2.f*i*t-2.f*k*t+i*k*t*t)*g;
222 		a1 = (2.f*j*t*t)*g;
223 		a2 = (-2.f*t+j*t*t)*g;
224 	}
225 
226 	freq = 1000.0 * 2.0 * M_PI / srate;
227 	std::complex<double> z = 1.0 / exp(std::complex<double>(0.0, freq));
228 	g = std::abs((std::complex<double>(b0) + double(b1) * z + double(b2) * z*z) / (std::complex<double>(1.0) + double(a1) * z + double(a2) * z*z));
229 	b0 /= g;
230 	b1 /= g;
231 	b2 /= g;
232 }
233 
run_filter(double in)234 double ZamPhonoPlugin::run_filter(double in)
235 {
236 	double out;
237 
238 	in = sanitize_denormal(in);
239 	out = in * b0 + zn1 * b1 + zn2 * b2
240 		      - zd1 * a1 - zd2 * a2;
241 	out = sanitize_denormal(out);
242 	zn2 = sanitize_denormal(zn1);
243 	zd2 = sanitize_denormal(zd1);
244 	zn1 = in;
245 	zd1 = out;
246 
247 	return out;
248 }
249 
run(const float ** inputs,float ** outputs,uint32_t frames)250 void ZamPhonoPlugin::run(const float** inputs, float** outputs, uint32_t frames)
251 {
252 	float srate = getSampleRate();
253 	int recalc = 0;
254 
255 	if (type != typeold) {
256 		recalc = 1;
257 	}
258 
259 	if (inv != invold) {
260 		recalc = 1;
261 	}
262 
263 	// Settings changed
264 	if (recalc) {
265 		// Clear filter states
266 		zn1 = zn2 = zd1 = zd2 = 0.0;
267 		clearbrickwall();
268 
269 		// Recalculate filter coeffs
270 		brickwall(std::min(0.45 * srate, 21000.), srate);
271 		emphasis(srate);
272 	}
273 
274 	double tmp;
275 	double in;
276 
277 	for (uint32_t i = 0; i < frames; i++) {
278 		in = inputs[0][i];
279 		tmp = run_filter(in);
280 		tmp = run_brickwall(tmp);
281 		outputs[0][i] = in;
282 		outputs[0][i] = (float)tmp;
283 	}
284 
285 	typeold = type;
286 	invold = inv;
287 }
288 
289 // -----------------------------------------------------------------------
290 
createPlugin()291 Plugin* createPlugin()
292 {
293 	return new ZamPhonoPlugin();
294 }
295 
296 // -----------------------------------------------------------------------
297 
298 END_NAMESPACE_DISTRHO
299