+ Monosynth: add inertia for pitch wheel
[calf.git] / src / calf / audio_fx.h
blob003abb10158b9989e8d355188388c0a3af757b04
1 /* Calf DSP Library
2 * Reusable audio effect classes.
4 * Copyright (C) 2001-2007 Krzysztof Foltman
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General
17 * Public License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
19 * Boston, MA 02111-1307, USA.
21 #ifndef __CALF_AUDIOFX_H
22 #define __CALF_AUDIOFX_H
24 #include <complex>
25 #include <iostream>
26 #include <calf/biquad.h>
27 #include "primitives.h"
28 #include "delay.h"
29 #include "fixed_point.h"
30 #include "inertia.h"
32 namespace dsp {
33 #if 0
34 }; to keep editor happy
35 #endif
37 /**
38 * Audio effect base class. Not really useful until it gets more developed.
40 class audio_effect
42 public:
43 virtual void setup(int sample_rate)=0;
44 virtual ~audio_effect() {}
47 class modulation_effect: public audio_effect
49 protected:
50 int sample_rate;
51 float rate, wet, dry, odsr;
52 gain_smoothing gs_wet, gs_dry;
53 public:
54 fixed_point<unsigned int, 20> phase, dphase;
55 float get_rate() {
56 return rate;
58 void set_rate(float rate) {
59 this->rate = rate;
60 dphase = rate/sample_rate*4096;
62 float get_wet() {
63 return wet;
65 void set_wet(float wet) {
66 this->wet = wet;
67 gs_wet.set_inertia(wet);
69 float get_dry() {
70 return dry;
72 void set_dry(float dry) {
73 this->dry = dry;
74 gs_dry.set_inertia(dry);
76 void reset_phase(float req_phase)
78 phase = req_phase * 4096.0;
80 void inc_phase(float req_phase)
82 phase += fixed_point<unsigned int, 20>(req_phase * 4096.0);
84 void setup(int sample_rate)
86 this->sample_rate = sample_rate;
87 this->odsr = 1.0 / sample_rate;
88 phase = 0;
89 set_rate(get_rate());
93 /**
94 * A monophonic phaser. If you want stereo, combine two :)
95 * Also, gave up on using template args for signal type.
97 template<int MaxStages>
98 class simple_phaser: public modulation_effect
100 protected:
101 float base_frq, mod_depth, fb;
102 float state;
103 int cnt, stages;
104 dsp::onepole<float, float> stage1;
105 float x1[MaxStages], y1[MaxStages];
106 public:
107 simple_phaser()
109 set_base_frq(1000);
110 set_mod_depth(1000);
111 set_fb(0);
112 state = 0;
113 cnt = 0;
114 stages = 0;
115 set_stages(6);
117 float get_base_frq() {
118 return base_frq;
120 void set_base_frq(float _base_frq) {
121 base_frq = _base_frq;
123 int get_stages() {
124 return stages;
126 void set_stages(int _stages) {
127 if (_stages > stages)
129 for (int i = stages; i < _stages; i++)
131 x1[i] = x1[stages-1];
132 y1[i] = y1[stages-1];
135 stages = _stages;
137 float get_mod_depth() {
138 return mod_depth;
140 void set_mod_depth(float _mod_depth) {
141 mod_depth = _mod_depth;
143 float get_fb() {
144 return fb;
146 void set_fb(float fb) {
147 this->fb = fb;
149 virtual void setup(int sample_rate) {
150 modulation_effect::setup(sample_rate);
151 reset();
153 void reset()
155 cnt = 0;
156 state = 0;
157 phase.set(0);
158 for (int i = 0; i < MaxStages; i++)
159 x1[i] = y1[i] = 0;
160 control_step();
162 inline void control_step()
164 cnt = 0;
165 int v = phase.get() + 0x40000000;
166 int sign = v >> 31;
167 v ^= sign;
168 // triangle wave, range from 0 to INT_MAX
169 double vf = (double)((v >> 16) * (1.0 / 16384.0) - 1);
171 float freq = base_frq * pow(2.0, vf * mod_depth / 1200.0);
172 freq = dsp::clip<float>(freq, 10.0, 0.49 * sample_rate);
173 stage1.set_ap_w(freq * (M_PI / 2.0) * odsr);
174 phase += dphase * 32;
175 for (int i = 0; i < stages; i++)
177 dsp::sanitize(x1[i]);
178 dsp::sanitize(y1[i]);
180 dsp::sanitize(state);
182 void process(float *buf_out, float *buf_in, int nsamples) {
183 for (int i=0; i<nsamples; i++) {
184 cnt++;
185 if (cnt == 32)
186 control_step();
187 float in = *buf_in++;
188 float fd = in + state * fb;
189 for (int j = 0; j < stages; j++)
190 fd = stage1.process_ap(fd, x1[j], y1[j]);
191 state = fd;
193 float sdry = in * gs_dry.get();
194 float swet = fd * gs_wet.get();
195 *buf_out++ = sdry + swet;
198 float freq_gain(float freq, float sr)
200 typedef std::complex<double> cfloat;
201 freq *= 2.0 * M_PI / sr;
202 cfloat z = 1.0 / exp(cfloat(0.0, freq)); // z^-1
204 cfloat p = cfloat(1.0);
205 cfloat stg = stage1.h_z(z);
207 for (int i = 0; i < stages; i++)
208 p = p * stg;
210 p = p / (cfloat(1.0) - cfloat(fb) * p);
211 return std::abs(cfloat(gs_dry.get_last()) + cfloat(gs_wet.get_last()) * p);
216 * Base class for chorus and flanger. Wouldn't be needed if it wasn't
217 * for odd behaviour of GCC when deriving templates from template
218 * base classes (not seeing fields from base classes!).
220 class chorus_base: public modulation_effect
222 protected:
223 int min_delay_samples, mod_depth_samples;
224 float min_delay, mod_depth;
225 sine_table<int, 4096, 65536> sine;
226 public:
227 float get_min_delay() {
228 return min_delay;
230 void set_min_delay(float min_delay) {
231 this->min_delay = min_delay;
232 this->min_delay_samples = (int)(min_delay * 65536.0 * sample_rate);
234 float get_mod_depth() {
235 return mod_depth;
237 void set_mod_depth(float mod_depth) {
238 this->mod_depth = mod_depth;
239 // 128 because it's then multiplied by (hopefully) a value of 32768..-32767
240 this->mod_depth_samples = (int)(mod_depth * 32.0 * sample_rate);
245 * Single-tap chorus without feedback.
246 * Perhaps MaxDelay should be a bit longer!
248 template<class T, int MaxDelay=512>
249 class simple_chorus: public chorus_base
251 protected:
252 simple_delay<MaxDelay,T> delay;
253 public:
254 simple_chorus() {
255 rate = 0.63f;
256 dry = 0.5f;
257 wet = 0.5f;
258 min_delay = 0.005f;
259 mod_depth = 0.0025f;
260 setup(44100);
262 void reset() {
263 delay.reset();
265 virtual void setup(int sample_rate) {
266 modulation_effect::setup(sample_rate);
267 delay.reset();
268 set_min_delay(get_min_delay());
269 set_mod_depth(get_mod_depth());
271 template<class OutIter, class InIter>
272 void process(OutIter buf_out, InIter buf_in, int nsamples) {
273 int mds = min_delay_samples + mod_depth_samples * 1024 + 2*65536;
274 int mdepth = mod_depth_samples;
275 for (int i=0; i<nsamples; i++) {
276 phase += dphase;
277 unsigned int ipart = phase.ipart();
279 float in = *buf_in++;
280 int lfo = phase.lerp_by_fract_int<int, 14, int>(sine.data[ipart], sine.data[ipart+1]);
281 int v = mds + (mdepth * lfo >> 6);
282 // if (!(i & 7)) printf("%d\n", v);
283 int ifv = v >> 16;
284 delay.put(in);
285 T fd; // signal from delay's output
286 delay.get_interp(fd, ifv, (v & 0xFFFF)*(1.0/65536.0));
287 T sdry = in * gs_dry.get();
288 T swet = fd * gs_wet.get();
289 *buf_out++ = sdry + swet;
295 * Single-tap flanger (chorus plus feedback).
297 template<class T, int MaxDelay=1024>
298 class simple_flanger: public chorus_base
300 protected:
301 simple_delay<MaxDelay,T> delay;
302 float fb;
303 int last_delay_pos, last_actual_delay_pos;
304 int ramp_pos, ramp_delay_pos;
305 public:
306 simple_flanger()
307 : fb(0) {}
308 void reset() {
309 delay.reset();
310 last_delay_pos = 0;
311 ramp_pos = 1024;
313 virtual void setup(int sample_rate) {
314 this->sample_rate = sample_rate;
315 this->odsr = 1.0 / sample_rate;
316 delay.reset();
317 phase = 0;
318 set_rate(get_rate());
319 set_min_delay(get_min_delay());
321 float get_fb() {
322 return fb;
324 void set_fb(float fb) {
325 this->fb = fb;
327 template<class OutIter, class InIter>
328 void process(OutIter buf_out, InIter buf_in, int nsamples) {
329 if (!nsamples)
330 return;
331 int mds = this->min_delay_samples + this->mod_depth_samples * 1024 + 2 * 65536;
332 int mdepth = this->mod_depth_samples;
333 int delay_pos;
334 unsigned int ipart = this->phase.ipart();
335 int lfo = phase.lerp_by_fract_int<int, 14, int>(this->sine.data[ipart], this->sine.data[ipart+1]);
336 delay_pos = mds + (mdepth * lfo >> 6);
338 if (delay_pos != last_delay_pos || ramp_pos < 1024)
340 if (delay_pos != last_delay_pos) {
341 // we need to ramp from what the delay tap length actually was,
342 // not from old (ramp_delay_pos) or desired (delay_pos) tap length
343 ramp_delay_pos = last_actual_delay_pos;
344 ramp_pos = 0;
347 int64_t dp = 0;
348 for (int i=0; i<nsamples; i++) {
349 float in = *buf_in++;
350 T fd; // signal from delay's output
351 dp = (((int64_t)ramp_delay_pos) * (1024 - ramp_pos) + ((int64_t)delay_pos) * ramp_pos) >> 10;
352 ramp_pos++;
353 if (ramp_pos > 1024) ramp_pos = 1024;
354 this->delay.get_interp(fd, dp >> 16, (dp & 0xFFFF)*(1.0/65536.0));
355 sanitize(fd);
356 T sdry = in * this->dry;
357 T swet = fd * this->wet;
358 *buf_out++ = sdry + swet;
359 this->delay.put(in+fb*fd);
361 this->phase += this->dphase;
362 ipart = this->phase.ipart();
363 lfo = phase.lerp_by_fract_int<int, 14, int>(this->sine.data[ipart], this->sine.data[ipart+1]);
364 delay_pos = mds + (mdepth * lfo >> 6);
366 last_actual_delay_pos = dp;
368 else {
369 for (int i=0; i<nsamples; i++) {
370 float in = *buf_in++;
371 T fd; // signal from delay's output
372 this->delay.get_interp(fd, delay_pos >> 16, (delay_pos & 0xFFFF)*(1.0/65536.0));
373 sanitize(fd);
374 T sdry = in * this->gs_dry.get();
375 T swet = fd * this->gs_wet.get();
376 *buf_out++ = sdry + swet;
377 this->delay.put(in+fb*fd);
379 this->phase += this->dphase;
380 ipart = this->phase.ipart();
381 lfo = phase.lerp_by_fract_int<int, 14, int>(this->sine.data[ipart], this->sine.data[ipart+1]);
382 delay_pos = mds + (mdepth * lfo >> 6);
384 last_actual_delay_pos = delay_pos;
386 last_delay_pos = delay_pos;
388 float freq_gain(float freq, float sr)
390 typedef std::complex<double> cfloat;
391 freq *= 2.0 * M_PI / sr;
392 cfloat z = 1.0 / exp(cfloat(0.0, freq)); // z^-1
394 float ldp = last_delay_pos / 65536.0;
395 float fldp = floor(ldp);
396 cfloat zn = std::pow(z, fldp); // z^-N
397 cfloat zn1 = zn * z; // z^-(N+1)
398 // simulate a lerped comb filter - H(z) = 1 / (1 + fb * (lerp(z^-N, z^-(N+1), fracpos))), N = int(pos), fracpos = pos - int(pos)
399 cfloat delayed = zn + (zn1 - zn) * cfloat(ldp - fldp);
400 cfloat h = cfloat(delayed) / (cfloat(1.0) - cfloat(fb) * delayed);
401 // mix with dry signal
402 float v = std::abs(cfloat(gs_dry.get_last()) + cfloat(gs_wet.get_last()) * h);
403 return v;
408 * A classic allpass loop reverb with modulated allpass filter.
409 * Just started implementing it, so there is no control over many
410 * parameters.
412 template<class T>
413 class reverb: public audio_effect
415 simple_delay<2048, T> apL1, apL2, apL3, apL4, apL5, apL6;
416 simple_delay<2048, T> apR1, apR2, apR3, apR4, apR5, apR6;
417 fixed_point<unsigned int, 25> phase, dphase;
418 sine_table<int, 128, 10000> sine;
419 onepole<T> lp_left, lp_right;
420 T old_left, old_right;
421 int type;
422 float time, fb, cutoff, diffusion;
423 int tl[6], tr[6];
424 float ldec[6], rdec[6];
426 int sr;
427 public:
428 reverb()
430 phase = 0.0;
431 time = 1.0;
432 cutoff = 9000;
433 type = 2;
434 diffusion = 1.f;
435 setup(44100);
437 virtual void setup(int sample_rate) {
438 sr = sample_rate;
439 set_time(time);
440 set_cutoff(cutoff);
441 phase = 0.0;
442 dphase = 0.5*128/sr;
443 update_times();
445 void update_times()
447 switch(type)
449 case 0:
450 tl[0] = 397 << 16, tr[0] = 383 << 16;
451 tl[1] = 457 << 16, tr[1] = 429 << 16;
452 tl[2] = 549 << 16, tr[2] = 631 << 16;
453 tl[3] = 649 << 16, tr[3] = 756 << 16;
454 tl[4] = 773 << 16, tr[4] = 803 << 16;
455 tl[5] = 877 << 16, tr[5] = 901 << 16;
456 break;
457 case 1:
458 tl[0] = 697 << 16, tr[0] = 783 << 16;
459 tl[1] = 957 << 16, tr[1] = 929 << 16;
460 tl[2] = 649 << 16, tr[2] = 531 << 16;
461 tl[3] = 1049 << 16, tr[3] = 1177 << 16;
462 tl[4] = 473 << 16, tr[4] = 501 << 16;
463 tl[5] = 587 << 16, tr[5] = 681 << 16;
464 break;
465 case 2:
466 default:
467 tl[0] = 697 << 16, tr[0] = 783 << 16;
468 tl[1] = 957 << 16, tr[1] = 929 << 16;
469 tl[2] = 649 << 16, tr[2] = 531 << 16;
470 tl[3] = 1249 << 16, tr[3] = 1377 << 16;
471 tl[4] = 1573 << 16, tr[4] = 1671 << 16;
472 tl[5] = 1877 << 16, tr[5] = 1781 << 16;
473 break;
474 case 3:
475 tl[0] = 1097 << 16, tr[0] = 1087 << 16;
476 tl[1] = 1057 << 16, tr[1] = 1031 << 16;
477 tl[2] = 1049 << 16, tr[2] = 1039 << 16;
478 tl[3] = 1083 << 16, tr[3] = 1055 << 16;
479 tl[4] = 1075 << 16, tr[4] = 1099 << 16;
480 tl[5] = 1003 << 16, tr[5] = 1073 << 16;
481 break;
482 case 4:
483 tl[0] = 197 << 16, tr[0] = 133 << 16;
484 tl[1] = 357 << 16, tr[1] = 229 << 16;
485 tl[2] = 549 << 16, tr[2] = 431 << 16;
486 tl[3] = 949 << 16, tr[3] = 1277 << 16;
487 tl[4] = 1173 << 16, tr[4] = 1671 << 16;
488 tl[5] = 1477 << 16, tr[5] = 1881 << 16;
489 break;
490 case 5:
491 tl[0] = 197 << 16, tr[0] = 133 << 16;
492 tl[1] = 257 << 16, tr[1] = 179 << 16;
493 tl[2] = 549 << 16, tr[2] = 431 << 16;
494 tl[3] = 619 << 16, tr[3] = 497 << 16;
495 tl[4] = 1173 << 16, tr[4] = 1371 << 16;
496 tl[5] = 1577 << 16, tr[5] = 1881 << 16;
497 break;
500 float fDec=1000 + 2400.f * diffusion;
501 for (int i = 0 ; i < 6; i++) {
502 ldec[i]=exp(-float(tl[i] >> 16) / fDec),
503 rdec[i]=exp(-float(tr[i] >> 16) / fDec);
506 float get_time() {
507 return time;
509 void set_time(float time) {
510 this->time = time;
511 // fb = pow(1.0f/4096.0f, (float)(1700/(time*sr)));
512 fb = 1.0 - 0.3 / (time * sr / 44100.0);
514 float get_type() {
515 return type;
517 void set_type(int type) {
518 this->type = type;
519 update_times();
521 float get_diffusion() {
522 return diffusion;
524 void set_diffusion(float diffusion) {
525 this->diffusion = diffusion;
526 update_times();
528 void set_type_and_diffusion(int type, float diffusion) {
529 this->type = type;
530 this->diffusion = diffusion;
531 update_times();
533 float get_fb()
535 return this->fb;
537 void set_fb(float fb)
539 this->fb = fb;
541 float get_cutoff() {
542 return cutoff;
544 void set_cutoff(float cutoff) {
545 this->cutoff = cutoff;
546 lp_left.set_lp(cutoff,sr);
547 lp_right.set_lp(cutoff,sr);
549 void reset()
551 apL1.reset();apR1.reset();
552 apL2.reset();apR2.reset();
553 apL3.reset();apR3.reset();
554 apL4.reset();apR4.reset();
555 apL5.reset();apR5.reset();
556 apL6.reset();apR6.reset();
557 lp_left.reset();lp_right.reset();
558 old_left = 0; old_right = 0;
560 void process(T &left, T &right)
562 unsigned int ipart = phase.ipart();
564 // the interpolated LFO might be an overkill here
565 int lfo = phase.lerp_by_fract_int<int, 14, int>(sine.data[ipart], sine.data[ipart+1]) >> 2;
566 phase += dphase;
568 left += old_right;
569 left = apL1.process_allpass_comb_lerp16(left, tl[0] - 45*lfo, ldec[0]);
570 left = apL2.process_allpass_comb_lerp16(left, tl[1] + 47*lfo, ldec[1]);
571 float out_left = left;
572 left = apL3.process_allpass_comb_lerp16(left, tl[2] + 54*lfo, ldec[2]);
573 left = apL4.process_allpass_comb_lerp16(left, tl[3] - 69*lfo, ldec[3]);
574 left = apL5.process_allpass_comb_lerp16(left, tl[4] + 69*lfo, ldec[4]);
575 left = apL6.process_allpass_comb_lerp16(left, tl[5] - 46*lfo, ldec[5]);
576 old_left = lp_left.process(left * fb);
577 sanitize(old_left);
579 right += old_left;
580 right = apR1.process_allpass_comb_lerp16(right, tr[0] - 45*lfo, rdec[0]);
581 right = apR2.process_allpass_comb_lerp16(right, tr[1] + 47*lfo, rdec[1]);
582 float out_right = right;
583 right = apR3.process_allpass_comb_lerp16(right, tr[2] + 54*lfo, rdec[2]);
584 right = apR4.process_allpass_comb_lerp16(right, tr[3] - 69*lfo, rdec[3]);
585 right = apR5.process_allpass_comb_lerp16(right, tr[4] + 69*lfo, rdec[4]);
586 right = apR6.process_allpass_comb_lerp16(right, tr[5] - 46*lfo, rdec[5]);
587 old_right = lp_right.process(right * fb);
588 sanitize(old_right);
590 left = out_left, right = out_right;
592 void extra_sanitize()
594 lp_left.sanitize();
595 lp_right.sanitize();
599 class filter_module_iface
601 public:
602 virtual void calculate_filter(float freq, float q, int mode, float gain = 1.0) = 0;
603 virtual void filter_activate() = 0;
604 virtual void sanitize() = 0;
605 virtual int process_channel(uint16_t channel_no, float *in, float *out, uint32_t numsamples, int inmask) = 0;
606 virtual float freq_gain(int subindex, float freq, float srate) = 0;
608 virtual ~filter_module_iface() {}
612 class biquad_filter_module: public filter_module_iface
614 private:
615 dsp::biquad_d1<float> left[3], right[3];
616 int order;
618 public:
619 uint32_t srate;
621 enum { mode_12db_lp = 0, mode_24db_lp = 1, mode_36db_lp = 2,
622 mode_12db_hp = 3, mode_24db_hp = 4, mode_36db_hp = 5,
623 mode_6db_bp = 6, mode_12db_bp = 7, mode_18db_bp = 8,
624 mode_6db_br = 9, mode_12db_br = 10, mode_18db_br = 11,
625 mode_count
628 public:
629 biquad_filter_module() : order(0) {}
631 void calculate_filter(float freq, float q, int mode, float gain = 1.0)
633 if (mode <= mode_36db_lp) {
634 order = mode + 1;
635 left[0].set_lp_rbj(freq, pow(q, 1.0 / order), srate, gain);
636 } else if ( mode_12db_hp <= mode && mode <= mode_36db_hp ) {
637 order = mode - mode_12db_hp + 1;
638 left[0].set_hp_rbj(freq, pow(q, 1.0 / order), srate, gain);
639 } else if ( mode_6db_bp <= mode && mode <= mode_18db_bp ) {
640 order = mode - mode_6db_bp + 1;
641 left[0].set_bp_rbj(freq, pow(q, 1.0 / order), srate, gain);
642 } else { // mode_6db_br <= mode <= mode_18db_br
643 order = mode - mode_6db_br + 1;
644 left[0].set_br_rbj(freq, order * 0.1 * q, srate, gain);
647 right[0].copy_coeffs(left[0]);
648 for (int i = 1; i < order; i++) {
649 left[i].copy_coeffs(left[0]);
650 right[i].copy_coeffs(left[0]);
654 void filter_activate()
656 for (int i=0; i < order; i++) {
657 left[i].reset();
658 right[i].reset();
662 void sanitize()
664 for (int i=0; i < order; i++) {
665 left[i].sanitize();
666 right[i].sanitize();
670 inline int process_channel(uint16_t channel_no, float *in, float *out, uint32_t numsamples, int inmask) {
671 dsp::biquad_d1<float> *filter;
672 switch (channel_no) {
673 case 0:
674 filter = left;
675 break;
677 case 1:
678 filter = right;
679 break;
681 default:
682 assert(false);
683 break;
686 if (inmask) {
687 switch(order) {
688 case 1:
689 for (uint32_t i = 0; i < numsamples; i++)
690 out[i] = filter[0].process(in[i]);
691 break;
692 case 2:
693 for (uint32_t i = 0; i < numsamples; i++)
694 out[i] = filter[1].process(filter[0].process(in[i]));
695 break;
696 case 3:
697 for (uint32_t i = 0; i < numsamples; i++)
698 out[i] = filter[2].process(filter[1].process(filter[0].process(in[i])));
699 break;
701 } else {
702 if (filter[order - 1].empty())
703 return 0;
704 switch(order) {
705 case 1:
706 for (uint32_t i = 0; i < numsamples; i++)
707 out[i] = filter[0].process_zeroin();
708 break;
709 case 2:
710 if (filter[0].empty())
711 for (uint32_t i = 0; i < numsamples; i++)
712 out[i] = filter[1].process_zeroin();
713 else
714 for (uint32_t i = 0; i < numsamples; i++)
715 out[i] = filter[1].process(filter[0].process_zeroin());
716 break;
717 case 3:
718 if (filter[1].empty())
719 for (uint32_t i = 0; i < numsamples; i++)
720 out[i] = filter[2].process_zeroin();
721 else
722 for (uint32_t i = 0; i < numsamples; i++)
723 out[i] = filter[2].process(filter[1].process(filter[0].process_zeroin()));
724 break;
727 for (int i = 0; i < order; i++)
728 filter[i].sanitize();
729 return filter[order - 1].empty() ? 0 : inmask;
732 float freq_gain(int subindex, float freq, float srate)
734 float level = 1.0;
735 for (int j = 0; j < order; j++)
736 level *= left[j].freq_gain(freq, srate);
737 return level;
741 #if 0
742 { to keep editor happy
743 #endif
746 #endif