/* * Copyright (c) 1983-2013 Richard Dobson and Composers Desktop Project Ltd * http://people.bath.ac.uk/masrwd * http://www.composersdesktop.com * This file is part of the CDP System. * The CDP System is free software; you can redistribute it * and/or modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * The CDP System is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public * License along with the CDP System; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ // reverberator.cpp: implementation of the reverberator class. // see also tapdelay.cpp //TODO: simple delay for use with tapped delayline // NB blame VC6 for horrible code, new returns null... ////////////////////////////////////////////////////////////////////// #include #include #include #include #ifdef _DEBUG #include #endif #include "reverberator.h" #ifndef PI # define PI 3.141592653589793238462643 #endif #ifndef TWOPI #define TWOPI (2.0 * PI) #endif #define ROOT2O2 (0.7071067811965475244) #define SPN 1.65436e-24 //RWD: use a proper MSVC version...? ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// //for example: //const deltap taps[4] = {{0.1,0.9},{0.25,.75},{0.5,.5},{0.618,.25}}; //NB this accepts a zero taptime tapdelay::tapdelay(const deltap *taps,unsigned int ctaps,double sr) : dtaps(taps) { tapbuf = 0; ntaps = ctaps; buflen = 0; reader = 0; tapptr = 0; tapcoeff = 0; srate = sr; output_gain = 1.0; max_gain = 1.0; } #define SAFETY (2) bool tapdelay::create(void) { #ifdef _DEBUG assert(tapbuf==0); assert(ntaps > 0); #endif if(tapbuf) return false; //or some informative errorval... if(ntaps <=0) return false; buflen = (unsigned int)((dtaps[ntaps-1].pos * srate) + SAFETY); tapbuf = new double[buflen]; if(!tapbuf) return false; memset(tapbuf,0,buflen*sizeof(double)); tapptr = new unsigned int[ntaps]; if(!tapptr){ delete [] tapbuf; tapbuf = 0; return false; } tapcoeff = new double[ntaps]; if(!tapcoeff) { delete [] tapbuf; tapbuf = 0; delete [] tapptr; tapptr = 0; return false; } //copy taps data, and get scalefac from total amps double sum = 0.0; for(unsigned int i = 0;i < ntaps;i++){ //RWD.10.98 allow zero taptime, for input mix control... if(dtaps[i].pos == 0.0) tapptr[i] = 0; else tapptr[i] = buflen - (int)(dtaps[i].pos * srate); tapcoeff[i] = dtaps[i].val; sum += fabs(tapcoeff[i]); #ifdef _DEBUG assert(tapptr[i] < buflen); #endif } //some idiot might give us empty taps! */ if(sum == 0.0){ delete [] tapbuf; tapbuf = 0; delete [] tapptr; tapptr = 0; delete [] tapcoeff; tapcoeff = 0; return false; } max_gain = (double)ntaps / sum; one_over_ntaps = 1.0 / (double)ntaps; return true; } double tapdelay::get_maxgain(void) { return max_gain; } void tapdelay::set_gain(double gain) { output_gain = gain; } double tapdelay::tick(double insam) { double val = 0.0; tapbuf[reader++] = insam; if(reader == buflen) reader = 0; for(unsigned int i = 0;i < ntaps;i++) { val += ((tapbuf[tapptr[i]++]) * (tapcoeff[i])); if(tapptr[i] == buflen) tapptr[i] = 0; } val *= one_over_ntaps; return val * output_gain; } tapdelay::~tapdelay() { delete [] tapbuf; delete [] tapptr; delete [] tapcoeff; } delay::delay(unsigned int len, double gain) { delbuf= 0; dgain = gain; ptr = 0; buflen = len; } delay::~delay() { delete delbuf; } bool delay::create(void) { if(buflen <= 0) return false; if(delbuf) return false; delbuf = new double[buflen]; if(!delbuf) { return false; } memset(delbuf,0,buflen*sizeof(double)); return true; } double delay::tick(double insam) { double val; val = delbuf[ptr]; delbuf[ptr++] = insam; if(ptr==buflen) ptr = 0; return val*dgain; } ////////////////////////////////////////////////////////////////////// // allpassfilter ////////////////////////////////////////////////////////////////////// //global helper functions: really belong in a new cdplib...except it's nice to have proper bool retval //straight from Csound: nreverb (vdelay.c) static bool prime( int val ) { int i, last; last = (int)sqrt( (double)val ); for ( i = 3; i <= last; i+=2 ) { if ( (val % i) == 0 ) return false; } return true; } unsigned int to_prime(double dur,double sr) { unsigned int time = (unsigned int) (dur * sr); if(time % 2== 0) time += 1; while(!prime(time)) time +=2; return time; } #define VMODMAX (0.5) //standard allpass, with optional pre_delay allpassfilter::allpassfilter(long sr,unsigned int buflen, double ingain,unsigned int predelay = 0) { vmod = 0.1; vfreq = 0.5; rvbuf1 = 0; pre_dline = 0; writer1 = reader1 = 0; sinelfo = 0; noiselfo = 0; srate = sr; rvblen = buflen + (unsigned int)(buflen * VMODMAX); gain = ingain; pre_delay = predelay; } bool allpassfilter::create(void) { if((gain < -1.0) || (gain > 1.0)) //do I want negative gain vals? return false; rvbuf1 = new double[rvblen]; if(!rvbuf1 ){ #ifdef _DEBUG printf("\nallpass: failed to allocate buffer size %d",rvblen); #endif return false; } if(pre_delay > 0){ pre_dline = new delay(pre_delay,1.0); if(!pre_dline->create()){ #ifdef _DEBUG printf("\nallpass: can't create pre_dline" ); #endif delete []rvbuf1; rvbuf1 = 0; return false; } } memset(rvbuf1,0,rvblen*sizeof(double)); sinelfo = new fastlfo(); sinelfo->init((double) srate,0.0,0,1); sinelfo->set_WaveType(LFO_SINE); sinelfo->set_freq(vfreq); sinelfo->set_mod(1.0); noiselfo = new fastlfo(); noiselfo->init((double) srate,0.0,0,1); noiselfo->set_WaveType(LFO_RAND_GAUSS); noiselfo->set_freq(vfreq * 2.5); noiselfo->set_mod(1.0); return true; } void allpassfilter::set_vmod(double amount) { double numod = VMODMAX; if(amount < numod) numod = amount; vmod = numod; } void allpassfilter::set_vfreq(double freq) { vfreq = freq; sinelfo->set_freq(vfreq); noiselfo->set_freq(vfreq * 2.5); } /* TODO: optimize this: separate funcs for pre-dline and plain */ double allpassfilter::tick(double insamp) { double output, input; input = insamp; output = (-gain) * input; output += rvbuf1[reader1++]; input += gain * output; if(pre_dline) rvbuf1[writer1++] = pre_dline->tick(input); else rvbuf1[writer1++] = input; if(reader1 == rvblen) reader1 = 0; if(writer1 == rvblen) writer1 = 0; return output; } /* TODO: implement this! */ double allpassfilter::vtick(double insamp) { double output, input; input = insamp; output = (-gain) * input; output += rvbuf1[reader1++]; input += gain * output; if(pre_dline) rvbuf1[writer1++] = pre_dline->tick(input); else rvbuf1[writer1++] = input; if(reader1 == rvblen) reader1 = 0; if(writer1 == rvblen) writer1 = 0; return output; } allpassfilter::~allpassfilter() { delete [] rvbuf1; delete pre_dline; } ////////////////////////////////////////////////////////////////////// //// nested_allpass //overall delay = ap1.pre_delay + ap2.pre_deay + ap1.length + ap2.length + post_delay nested_allpass::nested_allpass(double srate,double lpfreq, unsigned int outertime, unsigned int time1, unsigned int time2, double gain, double gain1, double gain2, unsigned int delay1 = 0, unsigned int delay2 = 0) { outer_gain = gain; outer_time = outertime; ap1_gain = gain1; ap2_gain = gain2; ap1_length = time1; ap2_length = time2; ap1_delay = delay1; ap2_delay = delay2; buf = 0; if(ap2_gain == 0.0) ap2_length = 0; ap1 = ap2 = 0; writer = reader = 0; lp = 0; lpfreq_ = lpfreq; sr_ = srate; } nested_allpass::~nested_allpass() { delete [] buf; delete ap1; delete ap2; //RWD delete lp; } bool nested_allpass::create(void) { if(outer_gain < -1.0 || outer_gain > 1.0) return false; if(outer_time <= 0) return false; buf = new double[outer_time]; if(!buf) return false; memset(buf,0,outer_time * sizeof(double)); if(ap1_length > 0 && ap1_gain > 0.0){ ap1 = new allpassfilter((long)sr_,ap1_length,ap1_gain,ap1_delay); if(!ap1->create()){ delete [] buf; buf = 0; return false; } } if(ap2_length >0 && ap2_gain > 0.0){ ap2 = new allpassfilter((long)sr_,ap2_length,ap2_gain,ap2_delay); if(!ap2->create()){ delete ap1; ap1 = 0; delete [] buf; buf = 0; return false; } } // RWD experiment: internal absorption damping lp = new onepole(lpfreq_,sr_,LOW_PASS); if(!lp){ delete ap1; ap1 = 0; delete ap2; ap2 = 0; delete [] buf; buf = 0; return false; } damping_ = false; return true; } //messy casts - sort this out later... double nested_allpass::tick(double insam) { double output, input,nest_out; input = insam; output = (-outer_gain) * input; output += buf[reader++]; input += outer_gain * output; //input is to two allpasses, and the post_delay, which acts as the overall allpass if(!ap2) { //no second allpass, may be one or no first allpass if(ap1) nest_out = ap1->tick(input); else //behave as normal allpass nest_out = input; } else nest_out = ap2->tick(ap1->tick(input)); //RWD experiment! if(damping_) buf[writer++] = lp->tick(nest_out); else buf[writer++] = nest_out; //wrap indices if(writer == outer_time) writer = 0; if(reader == outer_time) reader = 0; return output; } ////////////////////////////////////////////////////////////////////// // lpcomb ////////////////////////////////////////////////////////////////////// lpcomb::lpcomb(unsigned int buflen, double fbgain, double lpcoeff, double pre = 1.0) { combbuf = 0; lp = 0; gain = fbgain; lpgain = lpcoeff; rvblen = buflen; prescale = pre; reader1 = writer1 = 0; } bool lpcomb::create(void) { if(prescale <= 0.0) return false; if((gain < -1.0 ) || (gain > 1.0)) //do I want negative gain vals? return false; combbuf = new double[rvblen]; if(!combbuf) return false; lp = new lowpass1(lpgain); if(!lp){ delete [] combbuf; combbuf = 0; return false; } memset(combbuf,0,rvblen*sizeof(double)); return true; } //wet output only, unscaled double lpcomb::tick(double insam) { double input,output,lpsig; input = insam * prescale; output = combbuf[reader1++]; //do gain scaling outside lpsig = lp->tick(output); combbuf[writer1++] = input + (gain * lpsig) ; //feedback scaled output if(writer1 == rvblen) writer1 = 0; if(reader1 == rvblen) reader1 = 0; return output; } lpcomb::~lpcomb() { delete [] combbuf; delete lp; } ////////////////////////////////////////////////////////////////////// // lowpass : perhaps a bit ott as a class, but it allows fairly independent fiddling ////////////////////////////////////////////////////////////////////// lowpass1::lowpass1(double filtgain) { temp = output = 0.0; gain = filtgain; } lowpass1::~lowpass1() { } //IIR lowpass inline double lowpass1::tick(double insam) { output = temp; temp = insam + (output * gain); return output; } // NB allows zero frequency: straight pass-through onepole::onepole(double freq,double srate, int low_high) { if(freq == 0.0){ a1 = 1.0; a2 = 0.0; } else { b = 2.0 - cos(TWOPI * (freq/srate)); a2 = sqrt((b*b) - 1.0) - b; a1 = 1.0 + a2; if(low_high==HIGH_PASS) a2 = -a2; } lastout = 0.0; } onepole::~onepole() { //nothing to do } double onepole::tick(double input) { double output; output = a1*input - a2*lastout; lastout = output; return output; } //uses code from CDP eq.c tonecontrol::tonecontrol(double frq,double dbfac,int type,double srate) { freq = frq; boost = dbfac; sr = srate; tc_type = type; x1 = x2 = y1 = y2 = 0.0; a0 = a1 = a2 = b1 = b2 = 0.0; } tonecontrol::~tonecontrol() { //nothing to do; } bool tonecontrol::create(void) { if(sr <= 0.0) return false; if(freq <= 0.0) return false; //normalize freq freq /= sr; switch(tc_type){ case(LOW_SHELVE): lshelve(); break; case(HIGH_SHELVE): hshelve(); break; default: return false; break; } return true; } double tonecontrol::tick(double input) { double out; if(boost== 0.0) return input; out = ((a0 * input) + (a1 * x1) + (a2 * x2) - (b1 * y1) - (b2 * y2)); x2 = x1; x1 = input; y2 = y1; y1 = out; return out; } void tonecontrol::lshelve(void) { double a, A, F, tmp, b0, recipb0, asq, F2, gamma2, siggam2, gam2p1; double gamman, gammad, ta0, ta1, ta2, tb0, tb1, tb2, aa1, ab1; a = tan(PI * (freq - 0.25)); /* Warp factor */ asq = a * a; A = pow(10.0, boost/20.0); /* Cvt dB to factor */ if((boost < 6.0) && (boost > -6.0)) F = sqrt(A); else if (A > 1.0) F = A/sqrt(2.0); else F = A * sqrt(2.0); /* If |boost/cut| < 6dB, then doesn't make sense to use 3dB point. Use of root makes bandedge at half the boost/cut amount */ F2 = F * F; tmp = A * A - F2; if(fabs(tmp) <= SPN) gammad = 1; else gammad = pow( (F2 - 1)/ tmp, 0.25); /* Fourth root */ gamman = sqrt(A) * gammad; /* Once for the numerator */ gamma2 = gamman * gamman; gam2p1 = 1 + gamma2; siggam2 = 2 * ROOT2O2 * gamman; ta0 = gam2p1 + siggam2; ta1 = -2 * (1 - gamma2); ta2 = gam2p1 - siggam2; /* And again for the denominator */ gamma2 = gammad * gammad; gam2p1 = 1 + gamma2; siggam2 = 2 * ROOT2O2 * gammad; tb0 = gam2p1 + siggam2; tb1 = -2 * (1 - gamma2); tb2 = gam2p1 - siggam2; /* Now bilinear transform to proper centre frequency */ aa1 = a * ta1; a0 = ta0 + aa1 + asq * ta2; a1 = 2 * a * (ta0 + ta2) + (1 + asq) * ta1; a2 = asq * ta0 + aa1 + ta2; ab1 = a * tb1; b0 = tb0 + ab1 + asq * tb2; b1 = 2 * a * (tb0 + tb2) + (1 + asq) * tb1; b2 = asq * tb0 + ab1 + tb2; /* Normalise b0 to 1.0 for realisability */ recipb0 = 1 / b0; a0 *= recipb0; a1 *= recipb0; a2 *= recipb0; b1 *= recipb0; b2 *= recipb0; } void tonecontrol::hshelve(void) { double a, A, F, tmp, b0, recipb0, asq, F2, gamma2, siggam2, gam2p1; double gamman, gammad, ta0, ta1, ta2, tb0, tb1, tb2, aa1, ab1; a = tan(PI * (freq - 0.25)); /* Warp factor */ asq = a * a; A = pow(10.0, boost/20.0); /* Cvt dB to factor */ if(boost < 6.0 && boost > -6.0) F = sqrt(A); else if (A > 1.0) F = A/sqrt(2.0); else F = A * sqrt(2.0); /* If |boost/cut| < 6dB, then doesn't make sense to use 3dB point. Use of root makes bandedge at half the boost/cut amount */ F2 = F * F; tmp = A * A - F2; if(fabs(tmp) <= SPN) gammad = 1; else gammad = pow( (F2 - 1)/ tmp, 0.25); /* Fourth root */ gamman = sqrt(A) * gammad; /* Once for the numerator */ gamma2 = gamman * gamman; gam2p1 = 1 + gamma2; siggam2 = 2 * ROOT2O2 * gamman; ta0 = gam2p1 + siggam2; ta1 = -2 * (1 - gamma2); ta2 = gam2p1 - siggam2; ta1 = -ta1; /* And again for the denominator */ gamma2 = gammad * gammad; gam2p1 = 1 + gamma2; siggam2 = 2 * ROOT2O2 * gammad; tb0 = gam2p1 + siggam2; tb1 = -2 * (1 - gamma2); tb2 = gam2p1 - siggam2; tb1 = -tb1; /* Now bilinear transform to proper centre frequency */ aa1 = a * ta1; a0 = ta0 + aa1 + asq * ta2; a1 = 2 * a * (ta0 + ta2) + (1 + asq) * ta1; a2 = asq * ta0 + aa1 + ta2; ab1 = a * tb1; b0 = tb0 + ab1 + asq * tb2; b1 = 2 * a * (tb0 + tb2) + (1 + asq) * tb1; b2 = asq * tb0 + ab1 + tb2; /* Normalise b0 to 1.0 for realisability */ recipb0 = 1 / b0; a0 *= recipb0; a1 *= recipb0; a2 *= recipb0; b1 *= recipb0; b2 *= recipb0; } /***************** GARDNER DIFFUSERS ***************/ // NOTE: the published Gardner designs are here modified by adding a onepole lp filter // in the internal outer feedback loop of each nested allpass filter. // When combined with the overall feedback filter, this will of course reduce the reverb decay time. // The option exists to bypass this overall feedback filter, and rely solely on the internal ones. // NOTE: to get an infinite reverb, all feedback filters have to be bypassed by setting lp_freq to 0.0 // otherwise, the output will still decay eventually ! small_diffuser::small_diffuser(unsigned int pre_delay, const NESTDATA *apdata1, const NESTDATA *apdata2,double fb_gain,double lp_freq,double srate){ ap1_data = *apdata1; ap2_data = *apdata2; predelay_time = pre_delay; lpgain = fb_gain; lpfreq = lp_freq; ap1 = ap2 = 0; lp1 = 0; predelay = 0; out1 = out2 = 0.0; sr = srate; damping_ = false; } small_diffuser::~small_diffuser() { delete ap1; delete ap2; delete predelay; delete lp1; } bool small_diffuser::create(void) { if(ap1_data.time1 == 0 || ap1_data.gain1 <= 0.0){ #ifdef _DEBUG printf("\ndiffuser: bad parameters(1)"); #endif return false; } if(ap1_data.time2 ==0 || ap1_data.gain2 <= 0.0){ #ifdef _DEBUG printf("\ndiffuser: bad parameters(2)"); #endif return false; } if(ap2_data.time1 == 0 || ap2_data.gain1 < 0.0){ #ifdef _DEBUG printf("\ndiffuser: bad parameters(3)"); #endif return false; } if(ap2_data.time2 ==0 || ap2_data.gain2 < 0.0){ #ifdef _DEBUG printf("\ndiffuser: bad parameters(4)"); #endif return false; } if(sr <=0.0){ #ifdef _DEBUG printf("\ndiffuser: bad srate parameter)"); #endif return false; } if(lpfreq <0.0){ #ifdef _DEBUG printf("\ndiffuser: bad freq parameter)"); #endif return false; } ap1 = new nested_allpass(sr,lpfreq,ap1_data.time1,ap1_data.time2,ap1_data.time3, ap1_data.gain1,ap1_data.gain2,ap1_data.gain3); if(!ap1->create()){ #ifdef _DEBUG printf("\ndiffuser: can't create first allpass"); #endif return false; } if(ap2_data.gain1 != 0.0){ //allow ap to eliminate second block ap2 = new nested_allpass(sr,lpfreq,ap2_data.time1,ap2_data.time2,ap2_data.time3, ap2_data.gain1,ap2_data.gain2,ap2_data.gain3); if(!ap2->create()){ #ifdef _DEBUG printf("\ndiffuser: can't create second allpass"); #endif delete ap1; ap1 = 0; return false; } } if(predelay_time > 0){ predelay = new delay(predelay_time,1.0); if(!predelay->create()){ #ifdef _DEBUG printf("\ndiffuser: can't create predelay"); #endif delete ap1; ap1 = 0; delete ap2; ap2 = 0; return false; } } // NB if lpfreq == 0, onepole is no-op - returns input lp1 = new onepole(lpfreq,sr,LOW_PASS); if(lp1 == 0){ delete [] predelay; predelay = 0; delete ap1; ap1 = 0; delete ap2; ap2 = 0; return false; } ap1->set_damping(damping_); ap2->set_damping(damping_); return true; } double small_diffuser::tick(double insam) { double filter_out; double lp_in; double output; double ip; // may only contain one nested allpass filter: // input to filter is either out1 or out2 if(ap2) lp_in = out2; //= both in series else lp_in = out1; // RWD NOTE: if the damping_ mechanism is preferred, this filter can be omitted and ... filter_out = lp1->tick(lp_in); // ... just use this // filter_out = lp_in ; ip = insam + lpgain * filter_out; if(predelay) ip = predelay->tick(ip); out1 = ap1->tick( ip); if(ap2){ out2 = ap2->tick(out1); output = (out1 + out2) * 0.5; } else output = out1; return output; } //post-delay almost certainly doe not need to be prime... medium_diffuser::medium_diffuser(double post_delay,const NESTDATA *apdata1, const NESTDATA *apdata2, double gain, double lp_freq, double srate) { ap1_data = *apdata1; ap2_data = *apdata2; postdelay_time = to_prime(post_delay,srate); md_gain = gain; lpfreq = lp_freq; ap1 = ap2 = 0; ap3 = 0; lp1 = 0; delay1 = delay2 = postdelay = 0; sr = srate; out1 = out2 =out3 = 0.0; damping_ = false; } medium_diffuser::~medium_diffuser() { delete ap1; delete ap2; delete ap3; delete lp1; delete delay1; delete delay2; } bool medium_diffuser::create(void) { if(ap1_data.time1 == 0 || ap1_data.gain1 <= 0.0){ #ifdef _DEBUG printf("\nmedium_diffuser: bad parameters(1)"); #endif return false; } if(ap1_data.time2 ==0 || ap1_data.gain2 <= 0.0){ #ifdef _DEBUG printf("\nmedium diffuser: bad parameters(2)"); #endif return false; } if(ap2_data.time1 == 0 || ap2_data.gain1 < 0.0){ #ifdef _DEBUG printf("\nmedium diffuser: bad parameters(3)"); #endif return false; } if(ap2_data.time2 ==0 || ap2_data.gain2 < 0.0){ #ifdef _DEBUG printf("\nmedium diffuser: bad parameters(4)"); #endif return false; } if(sr <=0.0){ #ifdef _DEBUG printf("\nmedium diffuser: bad srate parameter)"); #endif return false; } if(lpfreq <0.0){ #ifdef _DEBUG printf("\nmedium diffuser: bad freq parameter)"); #endif return false; } ap1 = new nested_allpass(sr,lpfreq,ap1_data.time1,ap1_data.time2,ap1_data.time3, ap1_data.gain1,ap1_data.gain2,ap1_data.gain3); if(!ap1->create()){ #ifdef _DEBUG printf("\nmedium diffuser: can't create first diffuser"); #endif return false; } ap2 = new nested_allpass(sr,lpfreq,ap2_data.time1,ap2_data.time2,ap2_data.time3, ap2_data.gain1,ap2_data.gain2,ap2_data.gain3); if(!ap2->create()){ #ifdef _DEBUG printf("\nmedium diffuser: can't create second diffuser"); #endif delete ap1; ap1 = 0; return false; } ap3 = new allpassfilter((long)sr,to_prime(0.03,sr),0.5,to_prime(0.005,sr)); if(!ap3->create()){ #ifdef _DEBUG printf("\nmedium diffuser: can't create internal allpass filter"); #endif delete ap1; ap1 = 0; delete ap2; ap2 = 0; return false; } if(postdelay_time > 0){ postdelay = new delay(postdelay_time,1.0); if(!postdelay->create()){ #ifdef _DEBUG printf("\nmedium diffuser: can't create postdelay"); #endif delete ap1; ap1 = 0; delete ap2; ap2 = 0; delete ap3; ap3 = 0; return false; } } //internal fixed delays delay1 = new delay(to_prime(/*0.007*/0.067,sr),1.0); if(!delay1->create()){ #ifdef _DEBUG printf("\nmedium diffuser: can't create internal delay1"); #endif delete ap1; ap1 = 0; delete ap2; ap2 = 0; delete ap3; ap3 = 0; delete [] postdelay; postdelay = 0; return false; } delay2 = new delay(to_prime(0.015,sr),1.0); if(!delay2->create()){ #ifdef _DEBUG printf("\nmedium diffuser: can't create internal delay2"); #endif delete ap1; ap1 = 0; delete ap2; ap2 = 0; delete ap3; ap3 = 0; delete [] postdelay; postdelay = 0; delete delay1; delay1 = 0; return false; } lp1 = new onepole(lpfreq,sr,LOW_PASS); if(lp1 == 0){ delete ap1; ap1 = 0; delete ap2; ap2 = 0; delete ap3; ap3 = 0; delete [] postdelay; postdelay = 0; delete delay1; delay1 = 0; delete delay2; delay2 = 0; return false; } if(lpfreq == 0.0) damping_ = false; ap1->set_damping(damping_); ap2->set_damping(damping_); return true; } double medium_diffuser::tick(double insam) { double filter_out; double output; //feedback from postdelay, thru lp filter // RWD NOTE: if the damping_ mechanism is preferred, this filter can be omitted and... filter_out = md_gain * lp1->tick(postdelay->tick(out3)); // ... just use this //filter_out = (float)(md_gain * postdelay->tick(out3)); //first nested-allpass, takes input plus feedback out1 = ap1->tick(insam + filter_out); //inner allpass with predelay, followed by plain delay out2 = delay1->tick(ap3->tick(out1)); //second nested-allpass, takes direct input out3 = ap2->tick(insam + md_gain * delay2->tick(out2)); output = 0.5 * (out1 + out2 + out3); return output; } large_diffuser::large_diffuser(const NESTDATA *apdata1, const NESTDATA *apdata2, double gain, double lp_freq, double srate) { ap1_data = *apdata1; ap2_data = *apdata2; ld_gain = gain; lpfreq = lp_freq; ap1 = ap2 = 0; ap3 = ap4 = 0; lp1 = 0; delay1 = delay2 = delay3 = delay4 = 0; sr = srate; out1 = out2 =out3 = 0.0; damping_ = false; } large_diffuser::~large_diffuser() { delete ap1; delete ap2; delete ap3; delete ap4; delete lp1; delete delay1; delete delay2; delete delay3; delete delay4; } bool large_diffuser::create(void) { if(ap1_data.time1 == 0 || ap1_data.gain1 <= 0.0){ #ifdef _DEBUG printf("\nmedium_diffuser: bad parameters(1)"); #endif return false; } if(ap1_data.time2 ==0 || ap1_data.gain2 <= 0.0){ #ifdef _DEBUG printf("\nmedium diffuser: bad parameters(2)"); #endif return false; } if(ap2_data.time1 == 0 || ap2_data.gain1 < 0.0){ #ifdef _DEBUG printf("\nmedium diffuser: bad parameters(3)"); #endif return false; } if(ap2_data.time2 ==0 || ap2_data.gain2 < 0.0){ #ifdef _DEBUG printf("\nmedium diffuser: bad parameters(4)"); #endif return false; } if(sr <=0.0){ #ifdef _DEBUG printf("\nmedium diffuser: bad srate parameter)"); #endif return false; } if(lpfreq <0.0){ #ifdef _DEBUG printf("\nmedium diffuser: bad freq parameter)"); #endif return false; } ap1 = new nested_allpass(sr,lpfreq,ap1_data.time1,ap1_data.time2,ap1_data.time3, ap1_data.gain1,ap1_data.gain2,ap1_data.gain3); if(!ap1->create()){ #ifdef _DEBUG printf("\nlarge diffuser: can't create first nested_allpass"); #endif return false; } ap2 = new nested_allpass(sr,lpfreq,ap2_data.time1,ap2_data.time2,ap2_data.time3, ap2_data.gain1,ap2_data.gain2,ap2_data.gain3); if(!ap2->create()){ #ifdef _DEBUG printf("\nlarge diffuser: can't create second netsed_allpass"); #endif cleanup(); return false; } ap3 = new allpassfilter((long) sr,to_prime(0.008,sr),0.3); if(!ap3->create()){ #ifdef _DEBUG printf("\nlarge diffuser: can't create first internal allpass filter"); #endif cleanup(); return false; } ap4 = new allpassfilter((long)sr,to_prime(0.012,sr),0.3); if(!ap4->create()){ #ifdef _DEBUG printf("\nlarge diffuser: can't create second internal allpass filter"); #endif cleanup(); return false; } //internal fixed delays delay1 = new delay((unsigned int)(0.004 * sr),1.0); if(!delay1->create()){ #ifdef _DEBUG printf("\nlarge diffuser: can't create internal delay1"); #endif cleanup(); return false; } delay2 = new delay((unsigned int)(0.017 * sr),1.0); if(!delay2->create()){ #ifdef _DEBUG printf("\nlarge diffuser: can't create internal delay2"); #endif cleanup(); return false; } delay3 = new delay((unsigned int)(0.031 * sr),1.0); if(!delay3->create()){ #ifdef _DEBUG printf("\nlarge diffuser: can't create internal delay3"); #endif cleanup(); return false; } delay4 = new delay((unsigned int)(0.003 * sr),1.0); if(!delay4->create()){ #ifdef _DEBUG printf("\nlarge diffuser: can't create internal delay4"); #endif cleanup(); return false; } lp1 = new onepole(lpfreq,sr,LOW_PASS); if(lp1 == 0){ cleanup(); return false; } if(lpfreq == 0.0) damping_ = false; ap1->set_damping(damping_); ap2->set_damping(damping_); return true; } void large_diffuser::cleanup() { delete lp1; lp1 = 0; delete delay4; delay4 = 0; delete delay3; delay3 = 0; delete delay2; delay2 = 0; delete delay1; delay1 = 0; delete ap4; ap4 = 0; delete ap3; ap3 = 0; delete ap2; ap2 = 0; delete ap1; ap1 = 0; } // NOTE: the three output taps from the diffuser can lead to discrete slap-back echoes in the output. // possibly authentic, but may not suit all purposes. See rmverb.cpp: some delay lengths reduced from Gardner. // This is possible because delays are discrete: they do not share single delay line buffer. // true also of medium diffuser, but not so apparent as delays are smaller double large_diffuser::tick(double insam) { double filter_out; double output; //feedback from lpfilter // RWD NOTE: if the damping_ mechanism is preferred, this filter can be omitted and ... filter_out = ld_gain * lp1->tick(out3); // ... just use this //filter_out = (float)(ld_gain * out3); //first tap, from two plain allpasses, and trailing delay out1 = delay1->tick(ap4->tick(ap3->tick(insam + filter_out))); //second tap, from first nested allpass and delays at entrance and exit out2 = delay3->tick(ap1->tick(delay2->tick(out1))); //third tap, from second nested-allpass with leading delay out3 = ap2->tick(delay4->tick(out2)); //out3 also goes into lpfilter... see above //prescribed scale factors from Gardner output = (out1 * 0.34) + (out2 * 0.14) + (out3 * 0.14); // this seems to give relatively soft reverb tail: could try this: //output = (float)((out1 * 0.7) + (out2 * 0.25) + (out3 * 0.25)); return output; } moorer::moorer(const MOORERDATA *pdata,double reverbtime,double damping,double srate) { mdata = *pdata; reverb_time = reverbtime; dampfac = damping; sr = srate; comb1 = comb2 = comb3 = comb4 = comb5 = comb6 = 0; ap1 = ap2 = ap3 = ap4 = 0; out = 0.0; } moorer::~moorer() { delete comb1; delete comb2; delete comb3; delete comb4; delete comb5; delete comb6; delete ap1; delete ap2; delete ap3; delete ap4; } bool moorer::create(void) { fgain1 = 0.42 * dampfac; fgain2 = 0.46 * dampfac; fgain3 = 0.48 * dampfac; fgain4 = 0.49 * dampfac; fgain5 = 0.50 * dampfac; fgain6 = 0.52 * dampfac; scalefac = (1.0 / 6.0); cgain1 = exp(log(0.001)*(mdata.ctime1/reverb_time)) * (1. - fgain1); cgain2 = exp(log(0.001)*(mdata.ctime2/reverb_time)) * (1. - fgain2); cgain3 = exp(log(0.001)*(mdata.ctime3/reverb_time)) * (1. - fgain3); cgain4 = exp(log(0.001)*(mdata.ctime4/reverb_time)) * (1. - fgain4); cgain5 = exp(log(0.001)*(mdata.ctime5/reverb_time)) * (1. - fgain5); cgain6 = exp(log(0.001)*(mdata.ctime6/reverb_time)) * (1. - fgain6); comb1 = new lpcomb(to_prime(mdata.ctime1,sr),cgain1,fgain1,1.0); comb2 = new lpcomb(to_prime(mdata.ctime2,sr),cgain2,fgain2,1.0); comb3 = new lpcomb(to_prime(mdata.ctime3,sr),cgain3,fgain3,1.0); comb4 = new lpcomb(to_prime(mdata.ctime4,sr),cgain4,fgain4,1.0); comb5 = new lpcomb(to_prime(mdata.ctime5,sr),cgain5,fgain5,1.0); comb6 = new lpcomb(to_prime(mdata.ctime6,sr),cgain6,fgain6,1.0); ap1 = new allpassfilter((long)sr,to_prime(mdata.atime1,sr),-0.6,0); ap2 = new allpassfilter((long)sr,to_prime(mdata.atime2,sr),0.4,0); ap3 = new allpassfilter((long)sr,to_prime(mdata.atime3,sr),-0.61,0); ap4 = new allpassfilter((long)sr,to_prime(mdata.atime4,sr),0.39,0); if(! (comb1->create() && comb2->create() && comb3->create() && comb4->create() && comb5->create() && comb6->create() && ap1->create() && ap2->create() && ap3->create() && ap4->create() )) { cleanup(); return false; } return true; } double moorer::tick(double insam) { out = comb1->tick(insam) + comb2->tick(insam) + comb3->tick(insam) + comb4->tick(insam) + comb5->tick(insam) + comb6->tick(insam) ; out *= scalefac; out = ap4->tick(ap3->tick(ap2->tick(ap1->tick(out)))); return out; } void moorer::cleanup() { delete comb6; comb6 = 0; delete comb5; comb5 = 0; delete comb4; comb4 = 0; delete comb3; comb3 = 0; delete comb2; comb2 = 0; delete comb1; comb1 = 0; delete ap4; ap4 = 0; delete ap3; ap3 = 0; delete ap2; ap2 = 0; delete ap1; ap1 = 0; } /* vmtdelay *******************************/ vcomb4::vcomb4() { dl_buf = 0; dl_length = 0; dl_input_index = 0; dl_srate = 0.0; } vcomb4::~vcomb4() { if(dl_buf && dl_length > 0) delete [] dl_buf; } bool vcomb4::init(long srate,double length_secs) { unsigned long len_frames = (unsigned long)(srate * length_secs ); if(len_frames == 0) return false; /* round upwards to allow for interpolation */ len_frames++; /* reject init if already created*/ /* TODO: more sexy error checking/reporting... */ if(dl_buf) return false; try { dl_buf = new double[len_frames]; } catch(...){ return false; } /* for VC 6 */ if(dl_buf == 0) return false; for(unsigned long i = 0; i < len_frames;i++) dl_buf[i] = 0.0; dl_length = len_frames; dl_input_index = 0; dl_srate = srate; gain = 0.5; return true; } bool vcomb4::init(const MOORERDATA *p_mdata,double reverbtime,double damping,double sr) { if(!init((long) sr,reverbtime)) return false; return true; } double vcomb4::tick(double vdelay_1,double vdelay_2,double vdelay_3,double vdelay_4,double feedback,double input) { unsigned long base_readpos1,base_readpos2,base_readpos3,base_readpos4; unsigned long next_index1,next_index2,next_index3,next_index4; double vlength,dlength,frac; double readpos1,readpos2,readpos3,readpos4; double* buf = dl_buf; dlength = (double) dl_length; /* get maxlen, save a cast later on */ /* read pointer is vlength ~behind~ write pointer */ /* tap1*/ vlength = dlength - (vdelay_1 * dl_srate); vlength = vlength < dlength ? vlength : dlength; /* clip vdelay to max del length */ readpos1 = dl_input_index + vlength; base_readpos1 = (unsigned long) ( readpos1); if(base_readpos1 >= dl_length) /* wrap dl indices */ base_readpos1 -= dl_length; next_index1 = base_readpos1 + 1; if(next_index1 >= dl_length) next_index1 -= dl_length; /*tap2*/ vlength = dlength - (vdelay_2 * dl_srate); vlength = vlength < dlength ? vlength : dlength; readpos2 = dl_input_index + vlength; base_readpos2 = (unsigned long) ( readpos2); if(base_readpos2 >= dl_length) /* wrap dl indices */ base_readpos2 -= dl_length; next_index2 = base_readpos2 + 1; if(next_index2 >= dl_length) next_index2 -= dl_length; /*tap3*/ vlength = dlength - (vdelay_3 * dl_srate); vlength = vlength < dlength ? vlength : dlength; readpos3 = dl_input_index + vlength; base_readpos3 = (unsigned long) ( readpos3); if(base_readpos3 >= dl_length) /* wrap dl indices */ base_readpos3 -= dl_length; next_index3 = base_readpos3 + 1; if(next_index3 >= dl_length) next_index3 -= dl_length; /* tap4 */ vlength = dlength - (vdelay_4 * dl_srate); vlength = vlength < dlength ? vlength : dlength; readpos4 = dl_input_index + vlength; base_readpos4 = (unsigned long) ( readpos4); if(base_readpos4 >= dl_length) /* wrap dl indices */ base_readpos4 -= dl_length; next_index4 = base_readpos4 + 1; if(next_index4 >= dl_length) next_index4 -= dl_length; double outsum = 0.0; fb1 *= feedback; fb2 *= feedback; fb3 *= feedback; fb4 *= feedback; /* basic interp of variable delay pos */ frac = readpos1 - (int) readpos1; outsum += fb1 * (buf[base_readpos1]+((buf[next_index1] - buf[base_readpos1]) * frac)); frac = readpos2 - (int) readpos2; outsum += fb2 * (buf[base_readpos2]+((buf[next_index2] - buf[base_readpos2]) * frac)); frac = readpos3 - (int) readpos3; outsum += fb3 * (buf[base_readpos3]+((buf[next_index3] - buf[base_readpos3]) * frac)); frac = readpos4 - (int) readpos4; outsum += fb4 * (buf[base_readpos4]+((buf[next_index4] - buf[base_readpos4]) * frac)); /* how do we scale all this?*/ input += gain * (outsum); /* add in new sample + fraction of ouput, unscaled, for minimum decay at max feedback */ buf[dl_input_index++] = input + outsum * 0.25; if(dl_input_index == dl_length) dl_input_index = 0; return input + outsum * 0.25; }