zynaddsubfx

ZynAddSubFX open source synthesizer
Log | Files | Refs | Submodules | LICENSE

CombFilterBank.cpp (5195B)


      1 #include <cmath>
      2 #include "../Misc/Allocator.h"
      3 #include "../Misc/Util.h"
      4 #include "CombFilterBank.h"
      5 
      6 namespace zyn {
      7 
      8     CombFilterBank::CombFilterBank(Allocator *alloc, unsigned int samplerate_, int buffersize_, float initgain):
      9     inputgain(1.0f),
     10     outgain(1.0f),
     11     gainbwd(initgain),
     12     baseFreq(110.0f),
     13     nrOfStrings(0),
     14     memory(*alloc),
     15     samplerate(samplerate_),
     16     buffersize(buffersize_)
     17     {
     18         // setup the smoother for gain parameter
     19         gain_smoothing.sample_rate(samplerate/16);
     20         gain_smoothing.thresh(0.02f); // TBD: 2% jump audible?
     21         gain_smoothing.cutoff(1.0f);
     22         gain_smoothing.reset(gainbwd);
     23         pos_writer = 0;
     24     }
     25 
     26     CombFilterBank::~CombFilterBank()
     27     {
     28         setStrings(0, baseFreq);
     29     }
     30 
     31     void CombFilterBank::setStrings(unsigned int nrOfStringsNew, const float baseFreqNew)
     32     {
     33         // limit nrOfStringsNew
     34         nrOfStringsNew = min(NUM_SYMPATHETIC_STRINGS,nrOfStringsNew);
     35 
     36         if(nrOfStringsNew == nrOfStrings && baseFreqNew == baseFreq)
     37             return;
     38 
     39         const unsigned int mem_size_new = (int)ceilf(( (float)samplerate/baseFreqNew*1.03f + buffersize + 2)/16) * 16;
     40         if(mem_size_new == mem_size)
     41         {
     42             if(nrOfStringsNew>nrOfStrings)
     43             {
     44                 for(unsigned int i = nrOfStrings; i < nrOfStringsNew; ++i)
     45                 {
     46                     string_smps[i] = memory.valloc<float>(mem_size);
     47                     memset(string_smps[i], 0, mem_size*sizeof(float));
     48                 }
     49             }
     50             else if(nrOfStringsNew<nrOfStrings)
     51                 for(unsigned int i = nrOfStringsNew; i < nrOfStrings; ++i)
     52                     memory.devalloc(string_smps[i]);
     53         } else
     54         {
     55             // free the old buffers (wrong size for baseFreqNew)
     56             for(unsigned int i = 0; i < nrOfStrings; ++i)
     57                 memory.devalloc(string_smps[i]);
     58 
     59             // allocate buffers with new size
     60             for(unsigned int i = 0; i < nrOfStringsNew; ++i)
     61             {
     62                 string_smps[i] = memory.valloc<float>(mem_size_new);
     63                 memset(string_smps[i], 0, mem_size_new*sizeof(float));
     64             }
     65             // update mem_size and baseFreq
     66             mem_size = mem_size_new;
     67             baseFreq = baseFreqNew;
     68             // reset writer position
     69             pos_writer = 0;
     70         }
     71         // update nrOfStrings
     72         nrOfStrings = nrOfStringsNew;
     73     }
     74 
     75     inline float CombFilterBank::tanhX(const float x)
     76     {
     77         // Pade approximation of tanh(x) bound to [-1 .. +1]
     78         // https://mathr.co.uk/blog/2017-09-06_approximating_hyperbolic_tangent.html
     79         const float x2 = x*x;
     80         return (x*(105.0f+10.0f*x2)/(105.0f+(45.0f+x2)*x2));
     81     }
     82 
     83     inline float CombFilterBank::sampleLerp(const float *smp, const float pos) const {
     84         int poshi = (int)pos; // integer part (pos >= 0)
     85         float poslo = pos - (float) poshi; // decimal part
     86         // linear interpolation between samples
     87         return smp[poshi] + poslo * (smp[(poshi+1)%mem_size]-smp[poshi]);
     88     }
     89 
     90     void CombFilterBank::filterout(float *smp)
     91     {
     92         // no string -> no sound
     93         if (nrOfStrings==0) return;
     94 
     95         // interpolate gainbuf values over buffer length using value smoothing filter (lp)
     96         // this should prevent popping noise when controlled binary with 0 / 127
     97         // new control rate = samplerate / 16
     98         const unsigned int gainbufsize = buffersize / 16;
     99         float gainbuf[gainbufsize]; // buffer for value smoothing filter
    100         if (!gain_smoothing.apply( gainbuf, gainbufsize, gainbwd ) ) // interpolate the gain value
    101             std::fill(gainbuf, gainbuf+gainbufsize, gainbwd); // if nothing to interpolate (constant value)
    102 
    103         for (unsigned int i = 0; i < buffersize; ++i)
    104         {
    105             // apply input gain
    106             const float input_smp = smp[i]*inputgain;
    107 
    108             for (unsigned int j = 0; j < nrOfStrings; ++j)
    109             {
    110                 if (delays[j] == 0.0f) continue;
    111                 assert(float(mem_size)>delays[j]);
    112                 // calculate the feedback sample positions in the buffer
    113                 const float pos_reader = fmodf(float(pos_writer+mem_size) - delays[j], float(mem_size));
    114 
    115                 // sample at that position
    116                 const float sample = sampleLerp(string_smps[j], pos_reader);
    117                 string_smps[j][pos_writer] = input_smp + tanhX(sample*gainbuf[i/16]);
    118             }
    119             // mix output buffer samples to output sample
    120             smp[i]=0.0f;
    121             unsigned int nrOfActualStrings = 0;
    122             for (unsigned int j = 0; j < nrOfStrings; ++j)
    123                 if (delays[j] != 0.0f) {
    124                     smp[i] += string_smps[j][pos_writer];
    125                     nrOfActualStrings++;
    126                 }
    127 
    128             // apply output gain to sum of strings and
    129             // divide by nrOfStrings to get mean value
    130             // division by zero is catched at the beginning filterOut()
    131             smp[i] *= outgain / (float)nrOfActualStrings;
    132 
    133             // increment writing position
    134             ++pos_writer %= mem_size;
    135         }
    136     }
    137 }