Chris@0
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@0
|
2
|
Chris@0
|
3 #include <alloca.h>
|
Chris@0
|
4 #include <iostream>
|
Chris@0
|
5 #include <cmath>
|
Chris@0
|
6
|
Chris@0
|
7 #include <vst2.x/audioeffect.h>
|
Chris@0
|
8
|
Chris@0
|
9 #define FFTSIZE 1024
|
Chris@0
|
10
|
Chris@0
|
11 class Devuvuzelator : public AudioEffect
|
Chris@0
|
12 {
|
Chris@0
|
13 enum {
|
Chris@0
|
14 LowParam = 0,
|
Chris@0
|
15 HighParam = 1,
|
Chris@0
|
16 FundamentalParam = 2,
|
Chris@0
|
17 BandwidthParam = 3,
|
Chris@0
|
18 HarmonicsParam = 4,
|
Chris@0
|
19 ReductionParam = 5,
|
Chris@0
|
20 NumParams = 6
|
Chris@0
|
21 };
|
Chris@0
|
22
|
Chris@0
|
23 public:
|
Chris@0
|
24 Devuvuzelator(audioMasterCallback cb);
|
Chris@0
|
25 ~Devuvuzelator();
|
Chris@0
|
26
|
Chris@0
|
27 virtual void getEffectName(char *n) {
|
Chris@0
|
28 vst_strncpy(n, "Devuvuzelator", kVstMaxEffectNameLen);
|
Chris@0
|
29 }
|
Chris@0
|
30 virtual void getProductString(char *n) {
|
Chris@0
|
31 vst_strncpy(n, "Devuvuzelator", kVstMaxProductStrLen);
|
Chris@0
|
32 }
|
Chris@0
|
33 virtual void getVendorString(char *n) {
|
Chris@0
|
34 vst_strncpy(n, "Queen Mary, University of London", kVstMaxVendorStrLen);
|
Chris@0
|
35 }
|
Chris@0
|
36
|
Chris@0
|
37 virtual void setParameter(VstInt32 index, float value);
|
Chris@0
|
38 virtual float getParameter(VstInt32 index);
|
Chris@0
|
39 virtual void getParameterLabel(VstInt32 index, char* label);
|
Chris@0
|
40 virtual void getParameterDisplay(VstInt32 index, char* text);
|
Chris@0
|
41 virtual void getParameterName(VstInt32 index, char* text);
|
Chris@0
|
42
|
Chris@0
|
43 virtual void setSampleRate (float sampleRate) {
|
Chris@0
|
44 m_sampleRate = sampleRate;
|
Chris@0
|
45 AudioEffect::setSampleRate(sampleRate);
|
Chris@0
|
46 }
|
Chris@0
|
47
|
Chris@0
|
48 virtual void processReplacing (float** inputs, float** outputs, VstInt32 sampleFrames) {
|
Chris@0
|
49 m_input = inputs[0];
|
Chris@0
|
50 m_output = outputs[0];
|
Chris@0
|
51 runImpl(sampleFrames);
|
Chris@0
|
52 }
|
Chris@0
|
53
|
Chris@0
|
54 void reset();
|
Chris@0
|
55 void window(float *);
|
Chris@0
|
56 void runImpl(unsigned long);
|
Chris@0
|
57 void processFrame();
|
Chris@0
|
58 void processSpectralFrame();
|
Chris@0
|
59
|
Chris@0
|
60 static void fft(unsigned int n, bool inverse,
|
Chris@0
|
61 double *ri, double *ii, double *ro, double *io);
|
Chris@0
|
62
|
Chris@0
|
63 int m_sampleRate;
|
Chris@0
|
64 float *m_input;
|
Chris@0
|
65 float *m_output;
|
Chris@0
|
66
|
Chris@0
|
67 float m_high;
|
Chris@0
|
68 float m_low;
|
Chris@0
|
69 float m_fundamental;
|
Chris@0
|
70 float m_bandwidth;
|
Chris@0
|
71 float m_harmonics;
|
Chris@0
|
72 float m_reduction;
|
Chris@0
|
73
|
Chris@0
|
74 const int m_fftsize;
|
Chris@0
|
75 const int m_increment;
|
Chris@0
|
76 int m_fill;
|
Chris@0
|
77 int m_read;
|
Chris@0
|
78 float *m_buffer;
|
Chris@0
|
79 float *m_outacc;
|
Chris@0
|
80 double *m_real;
|
Chris@0
|
81 double *m_imag;
|
Chris@0
|
82 double *m_window;
|
Chris@0
|
83 };
|
Chris@0
|
84
|
Chris@0
|
85 void
|
Chris@0
|
86 Devuvuzelator::setParameter(VstInt32 index, float value)
|
Chris@0
|
87 {
|
Chris@0
|
88 float *params[NumParams] = {
|
Chris@0
|
89 m_low,
|
Chris@0
|
90 m_high,
|
Chris@0
|
91 m_fundamental,
|
Chris@0
|
92 m_bandwidth,
|
Chris@0
|
93 m_harmonics,
|
Chris@0
|
94 m_reduction,
|
Chris@0
|
95 };
|
Chris@0
|
96
|
Chris@0
|
97 *params[index] = value;
|
Chris@0
|
98 }
|
Chris@0
|
99
|
Chris@0
|
100 float
|
Chris@0
|
101 Devuvuzelator::getParameter(VstInt32 index)
|
Chris@0
|
102 {
|
Chris@0
|
103 float *params[NumParams] = {
|
Chris@0
|
104 m_low,
|
Chris@0
|
105 m_high,
|
Chris@0
|
106 m_fundamental,
|
Chris@0
|
107 m_bandwidth,
|
Chris@0
|
108 m_harmonics,
|
Chris@0
|
109 m_reduction,
|
Chris@0
|
110 };
|
Chris@0
|
111
|
Chris@0
|
112 return *params[index];
|
Chris@0
|
113 }
|
Chris@0
|
114
|
Chris@0
|
115 // NB! The max name length for VST parameter names, labels
|
Chris@0
|
116 // (i.e. units) and display values (i.e. string renderings of current
|
Chris@0
|
117 // value) is a rather amazing 8 bytes
|
Chris@0
|
118
|
Chris@0
|
119 void
|
Chris@0
|
120 Devuvuzelator::getParameterLabel(VstInt32 index, char *label)
|
Chris@0
|
121 {
|
Chris@0
|
122 const char *units[NumParams] = {
|
Chris@0
|
123 "dB",
|
Chris@0
|
124 "dB",
|
Chris@0
|
125 "Hz",
|
Chris@0
|
126 "Hz",
|
Chris@0
|
127 "",
|
Chris@0
|
128 "dB",
|
Chris@0
|
129 };
|
Chris@0
|
130
|
Chris@0
|
131 vst_strncpy(label, units[index], kVstMaxParamStrLen);
|
Chris@0
|
132 }
|
Chris@0
|
133
|
Chris@0
|
134 void
|
Chris@0
|
135 Devuvuzelator::getParameterDisplay(VstInt32 index, char *label)
|
Chris@0
|
136 {
|
Chris@0
|
137 snprintf(label, kVstMaxParamStrLen, "%f", getParameter(index));
|
Chris@0
|
138 }
|
Chris@0
|
139
|
Chris@0
|
140 void
|
Chris@0
|
141 Devuvuzelator::getParameterName(VstInt32 index, char *label)
|
Chris@0
|
142 {
|
Chris@0
|
143 const char *names[NumParams] = {
|
Chris@0
|
144 "Floor",
|
Chris@0
|
145 "Ceiling",
|
Chris@0
|
146 "Pitch",
|
Chris@0
|
147 "B/W",
|
Chris@0
|
148 "Partials",
|
Chris@0
|
149 "Reductn",
|
Chris@0
|
150 };
|
Chris@0
|
151
|
Chris@0
|
152 vst_strncpy(label, names[index], kVstMaxParamStrLen);
|
Chris@0
|
153 }
|
Chris@0
|
154
|
Chris@0
|
155 Devuvuzelator::Devuvuzelator(audioMasterCallback cb) :
|
Chris@0
|
156 AudioEffect(cb, 0, NumParams),
|
Chris@0
|
157 m_sampleRate(0),
|
Chris@0
|
158 m_input(0),
|
Chris@0
|
159 m_output(0),
|
Chris@0
|
160 m_low(0),
|
Chris@0
|
161 m_high(0),
|
Chris@0
|
162 m_fftsize(FFTSIZE),
|
Chris@0
|
163 m_increment(m_fftsize/4),
|
Chris@0
|
164 m_fill(0),
|
Chris@0
|
165 m_read(0)
|
Chris@0
|
166 {
|
Chris@0
|
167 m_buffer = new float[m_fftsize];
|
Chris@0
|
168 m_outacc = new float[m_fftsize * 2];
|
Chris@0
|
169 m_real = new double[m_fftsize];
|
Chris@0
|
170 m_imag = new double[m_fftsize];
|
Chris@0
|
171 m_window = new double[m_fftsize];
|
Chris@0
|
172
|
Chris@0
|
173 for (int i = 0; i < m_fftsize; ++i) {
|
Chris@0
|
174 m_window[i] = 0.5 - 0.5 * cos(2 * M_PI * i / m_fftsize);
|
Chris@0
|
175 }
|
Chris@0
|
176
|
Chris@0
|
177 m_low = -40;
|
Chris@0
|
178 m_high = -20;
|
Chris@0
|
179 m_fundamental = 220;
|
Chris@0
|
180 m_bandwidth = 60;
|
Chris@0
|
181 m_harmonics = 3;
|
Chris@0
|
182 m_reduction = 10;
|
Chris@0
|
183
|
Chris@0
|
184 setUniqueID("qmvz");
|
Chris@0
|
185 setNumInputs(1);
|
Chris@0
|
186 setNumOutputs(1);
|
Chris@0
|
187 canProcessReplacing(true);
|
Chris@0
|
188 canDoubleReplacing(false);
|
Chris@0
|
189
|
Chris@0
|
190 reset();
|
Chris@0
|
191 }
|
Chris@0
|
192
|
Chris@0
|
193 Devuvuzelator::~Devuvuzelator()
|
Chris@0
|
194 {
|
Chris@0
|
195 delete[] m_buffer;
|
Chris@0
|
196 delete[] m_outacc;
|
Chris@0
|
197 delete[] m_real;
|
Chris@0
|
198 delete[] m_imag;
|
Chris@0
|
199 delete[] m_window;
|
Chris@0
|
200 }
|
Chris@0
|
201
|
Chris@0
|
202 void
|
Chris@0
|
203 Devuvuzelator::reset()
|
Chris@0
|
204 {
|
Chris@0
|
205 for (int i = 0; i < m_fftsize; ++i) {
|
Chris@0
|
206 m_buffer[i] = 0.f;
|
Chris@0
|
207 }
|
Chris@0
|
208 for (int i = 0; i < m_fftsize*2; ++i) {
|
Chris@0
|
209 m_outacc[i] = 0.f;
|
Chris@0
|
210 }
|
Chris@0
|
211 m_fill = 0;
|
Chris@0
|
212 m_read = 0;
|
Chris@0
|
213 }
|
Chris@0
|
214
|
Chris@0
|
215 void
|
Chris@0
|
216 Devuvuzelator::runImpl(unsigned long sampleCount)
|
Chris@0
|
217 {
|
Chris@0
|
218 if (!m_input || !m_output) return;
|
Chris@0
|
219
|
Chris@0
|
220 int ii = 0;
|
Chris@0
|
221 int oi = 0;
|
Chris@0
|
222
|
Chris@0
|
223 while (ii < sampleCount) {
|
Chris@0
|
224
|
Chris@0
|
225 m_output[oi++] = m_outacc[m_read++] / 1.5f;
|
Chris@0
|
226
|
Chris@0
|
227 if (m_fill == m_fftsize) {
|
Chris@0
|
228
|
Chris@0
|
229 processFrame();
|
Chris@0
|
230
|
Chris@0
|
231 for (int j = m_increment; j < m_fftsize; ++j) {
|
Chris@0
|
232 m_buffer[j - m_increment] = m_buffer[j];
|
Chris@0
|
233 }
|
Chris@0
|
234
|
Chris@0
|
235 for (int j = m_increment; j < m_fftsize*2; ++j) {
|
Chris@0
|
236 m_outacc[j - m_increment] = m_outacc[j];
|
Chris@0
|
237 }
|
Chris@0
|
238
|
Chris@0
|
239 for (int j = m_fftsize*2 - m_increment; j < m_fftsize*2; ++j) {
|
Chris@0
|
240 m_outacc[j] = 0.f;
|
Chris@0
|
241 }
|
Chris@0
|
242
|
Chris@0
|
243 m_fill -= m_increment;
|
Chris@0
|
244 m_read -= m_increment;
|
Chris@0
|
245 }
|
Chris@0
|
246
|
Chris@0
|
247 m_buffer[m_fill++] = m_input[ii++];
|
Chris@0
|
248 }
|
Chris@0
|
249 }
|
Chris@0
|
250
|
Chris@0
|
251 void
|
Chris@0
|
252 Devuvuzelator::processFrame()
|
Chris@0
|
253 {
|
Chris@0
|
254 double *frame = (double *)alloca(m_fftsize * sizeof(double));
|
Chris@0
|
255 int ix = m_fftsize/2;
|
Chris@0
|
256 for (int i = 0; i < m_fftsize; ++i) {
|
Chris@0
|
257 frame[ix++] = m_buffer[i] * m_window[i];
|
Chris@0
|
258 if (ix == m_fftsize) ix = 0;
|
Chris@0
|
259 }
|
Chris@0
|
260
|
Chris@0
|
261 fft(m_fftsize, false, frame, 0, m_real, m_imag);
|
Chris@0
|
262
|
Chris@0
|
263 processSpectralFrame();
|
Chris@0
|
264
|
Chris@0
|
265 for (int i = 0; i < m_fftsize/2-1; ++i) {
|
Chris@0
|
266 m_real[m_fftsize-i] = m_real[i+1];
|
Chris@0
|
267 m_imag[m_fftsize-i] = -m_imag[i+1];
|
Chris@0
|
268 }
|
Chris@0
|
269
|
Chris@0
|
270 double *spare = (double *)alloca(m_fftsize * sizeof(double));
|
Chris@0
|
271 fft(m_fftsize, true, m_real, m_imag, frame, spare);
|
Chris@0
|
272
|
Chris@0
|
273 ix = m_fftsize/2;
|
Chris@0
|
274 for (int i = 0; i < m_fftsize; ++i) {
|
Chris@0
|
275 m_outacc[m_fftsize + i] += frame[ix++] * m_window[i];
|
Chris@0
|
276 if (ix == m_fftsize) ix = 0;
|
Chris@0
|
277 }
|
Chris@0
|
278 }
|
Chris@0
|
279
|
Chris@0
|
280 void
|
Chris@0
|
281 Devuvuzelator::processSpectralFrame()
|
Chris@0
|
282 {
|
Chris@0
|
283 const int hs = m_fftsize/2 + 1;
|
Chris@0
|
284 double *mags = (double *)alloca(hs * sizeof(double));
|
Chris@0
|
285 double *ratios = (double *)alloca(hs * sizeof(double));
|
Chris@0
|
286 for (int i = 0; i < hs; ++i) {
|
Chris@0
|
287 ratios[i] = 1.0;
|
Chris@0
|
288 mags[i] = sqrt(m_real[i] * m_real[i] + m_imag[i] * m_imag[i]);
|
Chris@0
|
289 }
|
Chris@0
|
290
|
Chris@0
|
291 double low = -35;
|
Chris@0
|
292 double high = -20;
|
Chris@0
|
293
|
Chris@0
|
294 if (m_low) low = *m_low;
|
Chris@0
|
295 if (m_high) high = *m_high;
|
Chris@0
|
296
|
Chris@0
|
297 int harmonics = 3;
|
Chris@0
|
298 if (m_harmonics) harmonics = int(*m_harmonics + 0.5);
|
Chris@0
|
299
|
Chris@0
|
300 double fun = 200;
|
Chris@0
|
301 if (m_fundamental) fun = *m_fundamental;
|
Chris@0
|
302
|
Chris@0
|
303 double bw = 40;
|
Chris@0
|
304 if (m_bandwidth) bw = *m_bandwidth;
|
Chris@0
|
305
|
Chris@0
|
306 double lowfun = fun - bw/2;
|
Chris@0
|
307 double highfun = fun + bw+2;
|
Chris@0
|
308
|
Chris@0
|
309 double reduction = 10;
|
Chris@0
|
310 if (m_reduction) reduction = *m_reduction;
|
Chris@0
|
311
|
Chris@0
|
312 for (int h = 0; h < harmonics; ++h) {
|
Chris@0
|
313
|
Chris@0
|
314 double lowfreq = lowfun * (h+1);
|
Chris@0
|
315 double highfreq = highfun * (h+1);
|
Chris@0
|
316
|
Chris@0
|
317 int lowbin = (m_fftsize * lowfreq) / m_sampleRate;
|
Chris@0
|
318 int highbin = (m_fftsize * highfreq) / m_sampleRate;
|
Chris@0
|
319
|
Chris@0
|
320 for (int i = lowbin; i <= highbin; ++i) {
|
Chris@0
|
321 ratios[i] = 1.0;
|
Chris@0
|
322 double db = 10 * log10(mags[i]);
|
Chris@0
|
323 if (db > low && db < high) {
|
Chris@0
|
324 double r = reduction;
|
Chris@0
|
325 ratios[i] = pow(10, -r / 10);
|
Chris@0
|
326 }
|
Chris@0
|
327 }
|
Chris@0
|
328 }
|
Chris@0
|
329
|
Chris@0
|
330 for (int i = 0; i < hs-1; ++i) {
|
Chris@0
|
331 if (ratios[i] == 1.0 && ratios[i+1] < 1.0) {
|
Chris@0
|
332 ratios[i] = (ratios[i+1] + 1) / 2;
|
Chris@0
|
333 } else if (ratios[i] < 1.0 && ratios[i+1] == 1.0) {
|
Chris@0
|
334 ratios[i+1] = (ratios[i] + 1) / 2;
|
Chris@0
|
335 ++i;
|
Chris@0
|
336 }
|
Chris@0
|
337 }
|
Chris@0
|
338
|
Chris@0
|
339 for (int i = 0; i < hs; ++i) {
|
Chris@0
|
340 m_real[i] *= ratios[i];
|
Chris@0
|
341 m_imag[i] *= ratios[i];
|
Chris@0
|
342 }
|
Chris@0
|
343 }
|
Chris@0
|
344
|
Chris@0
|
345 // FFT implementation by Don Cross, public domain.
|
Chris@0
|
346 // This version scales the forward transform.
|
Chris@0
|
347
|
Chris@0
|
348 void Devuvuzelator::fft(unsigned int n, bool inverse,
|
Chris@0
|
349 double *ri, double *ii, double *ro, double *io)
|
Chris@0
|
350 {
|
Chris@0
|
351 if (!ri || !ro || !io) return;
|
Chris@0
|
352
|
Chris@0
|
353 unsigned int bits;
|
Chris@0
|
354 unsigned int i, j, k, m;
|
Chris@0
|
355 unsigned int blockSize, blockEnd;
|
Chris@0
|
356
|
Chris@0
|
357 double tr, ti;
|
Chris@0
|
358
|
Chris@0
|
359 if (n < 2) return;
|
Chris@0
|
360 if (n & (n-1)) return;
|
Chris@0
|
361
|
Chris@0
|
362 double angle = 2.0 * M_PI;
|
Chris@0
|
363 if (inverse) angle = -angle;
|
Chris@0
|
364
|
Chris@0
|
365 for (i = 0; ; ++i) {
|
Chris@0
|
366 if (n & (1 << i)) {
|
Chris@0
|
367 bits = i;
|
Chris@0
|
368 break;
|
Chris@0
|
369 }
|
Chris@0
|
370 }
|
Chris@0
|
371
|
Chris@0
|
372 static unsigned int tableSize = 0;
|
Chris@0
|
373 static int *table = 0;
|
Chris@0
|
374
|
Chris@0
|
375 if (tableSize != n) {
|
Chris@0
|
376
|
Chris@0
|
377 delete[] table;
|
Chris@0
|
378
|
Chris@0
|
379 table = new int[n];
|
Chris@0
|
380
|
Chris@0
|
381 for (i = 0; i < n; ++i) {
|
Chris@0
|
382
|
Chris@0
|
383 m = i;
|
Chris@0
|
384
|
Chris@0
|
385 for (j = k = 0; j < bits; ++j) {
|
Chris@0
|
386 k = (k << 1) | (m & 1);
|
Chris@0
|
387 m >>= 1;
|
Chris@0
|
388 }
|
Chris@0
|
389
|
Chris@0
|
390 table[i] = k;
|
Chris@0
|
391 }
|
Chris@0
|
392
|
Chris@0
|
393 tableSize = n;
|
Chris@0
|
394 }
|
Chris@0
|
395
|
Chris@0
|
396 if (ii) {
|
Chris@0
|
397 for (i = 0; i < n; ++i) {
|
Chris@0
|
398 ro[table[i]] = ri[i];
|
Chris@0
|
399 io[table[i]] = ii[i];
|
Chris@0
|
400 }
|
Chris@0
|
401 } else {
|
Chris@0
|
402 for (i = 0; i < n; ++i) {
|
Chris@0
|
403 ro[table[i]] = ri[i];
|
Chris@0
|
404 io[table[i]] = 0.0;
|
Chris@0
|
405 }
|
Chris@0
|
406 }
|
Chris@0
|
407
|
Chris@0
|
408 blockEnd = 1;
|
Chris@0
|
409
|
Chris@0
|
410 for (blockSize = 2; blockSize <= n; blockSize <<= 1) {
|
Chris@0
|
411
|
Chris@0
|
412 double delta = angle / (double)blockSize;
|
Chris@0
|
413 double sm2 = -sin(-2 * delta);
|
Chris@0
|
414 double sm1 = -sin(-delta);
|
Chris@0
|
415 double cm2 = cos(-2 * delta);
|
Chris@0
|
416 double cm1 = cos(-delta);
|
Chris@0
|
417 double w = 2 * cm1;
|
Chris@0
|
418 double ar[3], ai[3];
|
Chris@0
|
419
|
Chris@0
|
420 for (i = 0; i < n; i += blockSize) {
|
Chris@0
|
421
|
Chris@0
|
422 ar[2] = cm2;
|
Chris@0
|
423 ar[1] = cm1;
|
Chris@0
|
424
|
Chris@0
|
425 ai[2] = sm2;
|
Chris@0
|
426 ai[1] = sm1;
|
Chris@0
|
427
|
Chris@0
|
428 for (j = i, m = 0; m < blockEnd; j++, m++) {
|
Chris@0
|
429
|
Chris@0
|
430 ar[0] = w * ar[1] - ar[2];
|
Chris@0
|
431 ar[2] = ar[1];
|
Chris@0
|
432 ar[1] = ar[0];
|
Chris@0
|
433
|
Chris@0
|
434 ai[0] = w * ai[1] - ai[2];
|
Chris@0
|
435 ai[2] = ai[1];
|
Chris@0
|
436 ai[1] = ai[0];
|
Chris@0
|
437
|
Chris@0
|
438 k = j + blockEnd;
|
Chris@0
|
439 tr = ar[0] * ro[k] - ai[0] * io[k];
|
Chris@0
|
440 ti = ar[0] * io[k] + ai[0] * ro[k];
|
Chris@0
|
441
|
Chris@0
|
442 ro[k] = ro[j] - tr;
|
Chris@0
|
443 io[k] = io[j] - ti;
|
Chris@0
|
444
|
Chris@0
|
445 ro[j] += tr;
|
Chris@0
|
446 io[j] += ti;
|
Chris@0
|
447 }
|
Chris@0
|
448 }
|
Chris@0
|
449
|
Chris@0
|
450 blockEnd = blockSize;
|
Chris@0
|
451 }
|
Chris@0
|
452
|
Chris@0
|
453 if (!inverse) {
|
Chris@0
|
454
|
Chris@0
|
455 double denom = (double)n;
|
Chris@0
|
456
|
Chris@0
|
457 for (i = 0; i < n; i++) {
|
Chris@0
|
458 ro[i] /= denom;
|
Chris@0
|
459 io[i] /= denom;
|
Chris@0
|
460 }
|
Chris@0
|
461 }
|
Chris@0
|
462 }
|
Chris@0
|
463
|
Chris@0
|
464 AudioEffect *createEffectInstance(audioMasterCallback audioMaster)
|
Chris@0
|
465 {
|
Chris@0
|
466 return new Devuvuzelator(audioMaster);
|
Chris@0
|
467 }
|
Chris@0
|
468
|