I decided to make a small program for filter design, to practise C++. I intend to also add a GUI, later on (Qt, most probably), so I thought that the way the program would go in the main() should be very modular and flexible, to be able to be used by widgets. I also thought I should be able to call only a filter at a time, to avoid having too many objects in memory, but also be able to call more than one filter, based on a loop (for comparisons), or the same filter with different parameters (same reason), also in a loop.Currently, only console application, to be run with:./filter {sampling_frequency} {corner_freq} {stopband_freq} {center_freq} {passband_attenuation/ripple} {stopband_attenuation/ripple} {custom_passband_attenuation} {DC_gain_normalization} {order} {design} {filter_type}Only Butterworth and Chebyshev are implemented, analog only, command line parameters can be left empty (there are defaults). As it is, the main() now loops through the two filters and prints the poles and the Laplace transfer function. Try different settings for corner_freq, stopband_freq, passband_att, stopband_att, custom_att (set sampling_freq=0). For example ./filter 0 1 1.2, or ./filter 0 1 2 1 0.1 40.This is a thinned down version of what it currently is, compiles, and works as intended, split into separate files. It's ~650 lines. I asked on chat and they said it's fine.Filter.h#ifndef FILTER_H_INCLUDED#define FILTER_H_INCLUDED#include <vector>#include <string>#include <iostream>class Filter{protected:    struct Types    {        enum Type        {            BUTTERWORTH             =  0,            CHEBYSHEV               =  1,            TYPE_ALL                =  2        };        std::string names[TYPE_ALL] {Butterworth, Chebyshev};        const Type type(const int8_t &i) const { return (Type)i; }    };    Types  *m_types;    int8_t  m_filterCase;                       // see enum Case    double  m_gain;    double  m_fp, m_fs, m_fp2, m_fc;            // analog/IIR frequencies    double  m_wscale;                           // frequency scaling    double  m_wc;                               // BP/BS center frequency    double  m_freqRatio;                        // fp/fs for all cases    double  m_bpRatio;                          // bandwidth / center frequency    double  m_Kp, m_Ks, m_Ksc;                  // attenuation specifications    // order related    short   m_N;                                // order    short   m_midN, m_midSize;                  // floor(N/2), ceil(N/2)    bool    m_oddN;                             // odd N => true    short   m_vSize;                            // vector size    // poles have a real and an imaginary part.    std::vector<double> m_real, m_imag;         // analog+IIR poles    std::vector<double> m_pole_SQ;              // lowpass prototype, ready-squaredpublic:    Filter() {std::cout<<Roots\n;}    virtual ~Filter() = default;    enum Case    {        CASE_ALLPASS    = 0,        CASE_LOWPASS    = 1,        CASE_HIGHPASS   = 2,        CASE_BANDPASS   = 3,        CASE_BANDSTOP   = 4,        CASE_ALL        = 5    };    enum Design    {        DESIGN_D1   = 1,    // higher As@fs, default        DESIGN_D2   = 2,    // lower Ap@fp    };    // Sets bandwidth and center frequency for BP/BS, prewarps for BLT    Filter(const double &f0,    \           const double &fp,    \           const double &fs,    \           const double &fc,    \           const double &Ap,    \           const double &As,    \           const double &Asc = 0);    // Sets vector lengths according to the order    void setVars(const double &fc);    // Stores the a0 and b0 terms for calculating m_gain later    void storeLPproto();    // Frequency transformations from the lowpass prototype    void freqTrans();    // set Laplace transfer function coefficients    void setLaplace(std::vector<double> &a2,    \                    std::vector<double> &a1,    \                    std::vector<double> &a0,    \                    std::vector<double> &b1,    \                    std::vector<double> &b0);    // gain    void setGain(const bool &normDC, const int8_t &filterType);    const double    getGain()   const { return m_gain; }    const short     getN()      const { return m_N; }    const short     getMidN()   const { return m_midN; }    const double    getVsize()  const { return m_vSize; }    const double    getWscale() const { return m_wscale; }    const double    getReal(const int8_t &i) const { return m_real[i]; }    const double    getImag(const int8_t &i) const { return m_imag[i]; }    Types* getType() { return m_types; }    // not working};#endif // FILTER_H_INCLUDEDFilter.cpp#include Filter.h#include <cmath>Filter::Filter(const double &f0,    \               const double &fp,    \               const double &fs,    \               const double &fc,    \               const double &Ap,    \               const double &As,    \               const double &Asc): m_types {new Types}{    // characteristic function limits    m_Kp = sqrt(pow(10, Ap*0.1) - 1);    m_Ks = sqrt(pow(10, As*0.1) - 1);    m_Ksc = sqrt(pow(10, 0.1*Asc) - 1);    /* m_filterCase is universally accessible, so account for AP/LP, too, even     * if the switch/case in freqTrans() doesn't use CASE_ALLPASS or     * CASE_LOWPASS.     */    if (fp == fs)        m_filterCase = 0;    else        m_filterCase = (fc ? 3+(fp>fs) : 1+(fp>fs));    // If the sampling frequency is set, prewarp the frequencies    if (f0)    {        if (fc)        {            m_fp  = tan(M_PI / f0 * (sqrt(fp*fp + 4 * (fc*fc)) - fp) * 0.5);            m_fp2 = tan(M_PI / f0 * (sqrt(fp*fp + 4 * (fc*fc)) + fp) * 0.5);            m_fs  = tan(M_PI / f0 * (sqrt(fs*fs + 4 * (fc*fc)) - fs) * 0.5);            m_fc  = tan(M_PI*fc/f0);        }        else        {            m_fp  = tan(M_PI / f0 * fp);            m_fp2 = 0;            m_fs  = tan(M_PI / f0 * fs);            m_fc  = 0;        }    }    else    {        m_fp = fp;        m_fs = fs;        m_fc = fc;    }    // Different bandwidth / center frequency    if (f0)        m_bpRatio = m_fp2 - m_fp;    else        m_bpRatio = m_fp / m_fc;    // Same for frequency ratios    if (f0)        m_freqRatio = (fc ? m_bpRatio*m_fs / (m_fp*m_fp2 - m_fs*m_fs) : m_fp/m_fs);    else        m_freqRatio = m_fp/m_fs - 0.5*(m_filterCase==CASE_ALLPASS);    m_freqRatio = std::min(m_freqRatio, 1/m_freqRatio);    // wc is different for IIRs. Note: m_wc is squared.    if (f0)        m_wc = m_fp * (fc ? m_fp2 : m_fp);    else        m_wc = (fc ? m_fc*m_fc : 1);}void Filter::setVars(const double &fc){    m_oddN = m_N&1;     // m_N%2    m_midN = m_N>>1;    // floor(m_N/2)    m_midSize = m_midN + m_oddN;    m_vSize = (fc ? m_N : m_midSize);    m_real.resize(m_vSize);    m_imag.resize(m_vSize);}void Filter::storeLPproto(){    // lowpass prototype poles, used in calculation of gain    m_pole_SQ.resize(m_midSize);    for (int8_t i=0; i<m_midSize; ++i)        m_pole_SQ[i] = m_real[i]*m_real[i] + m_imag[i]*m_imag[i];}void Filter::freqTrans(){    double magnitude, angle, realPart, imagPart;    double bpRatio_SQ {m_bpRatio*m_bpRatio};    switch (m_filterCase)    {    case 2:        for (short i=0; i<m_midN; ++i)        {            m_real[i] /= m_pole_SQ[i];            m_imag[i] /= m_pole_SQ[i];        }        if (m_oddN)            m_real[m_midN] /= m_pole_SQ[m_midN];        break;    case 3:        for (short i=0; i<m_midN; ++i)        {            realPart = bpRatio_SQ * (m_real[i]*m_real[i] - m_imag[i]*m_imag[i]) - 4 * m_wc;            imagPart = 2 * bpRatio_SQ * m_real[i] * m_imag[i];            magnitude = sqrt(hypot(imagPart, realPart));            angle = atan2(imagPart, realPart) * 0.5;            m_real[i+m_midSize] = (m_bpRatio * m_real[i] + magnitude * cos(angle)) * 0.5;            m_imag[i+m_midSize] = (magnitude * sin(angle) + m_bpRatio * m_imag[i]) * 0.5;            m_real[i] = (m_bpRatio * m_real[i] - magnitude * cos(angle)) * 0.5;            m_imag[i] = (magnitude * sin(angle) - m_bpRatio * m_imag[i]) * 0.5;        }        if (m_oddN)            m_real[m_midN] = m_bpRatio * m_real[m_midN] * 0.5;        break;    case 4:        for (short i=0; i<m_midN; ++i)        {            realPart = bpRatio_SQ * (m_real[i]*m_real[i] - m_imag[i]*m_imag[i]) /                        (m_pole_SQ[i]*m_pole_SQ[i]) - 4 * m_wc;            imagPart = 2 * bpRatio_SQ * m_real[i] * m_imag[i] /                        (m_pole_SQ[i]*m_pole_SQ[i]);            magnitude = sqrt(hypot(imagPart, realPart));            angle = atan2(imagPart, realPart) * 0.5;            m_real[i+m_midSize] = (m_bpRatio * m_real[i] / m_pole_SQ[i] +                        magnitude * cos(angle)) * 0.5;            m_imag[i+m_midSize] = (magnitude * sin(angle) +                        m_bpRatio * m_imag[i] / m_pole_SQ[i]) * 0.5;            m_real[i] = (m_bpRatio * m_real[i] / m_pole_SQ[i] -                        magnitude * cos(angle)) * 0.5;            m_imag[i] = (magnitude * sin(angle) -                        m_bpRatio * m_imag[i] / m_pole_SQ[i]) * 0.5;        }        if (m_oddN)            m_real[m_midN] = m_bpRatio * m_real[m_midN] / m_pole_SQ[m_midN] * 0.5;        break;    }}void Filter::setLaplace(std::vector<double> &a2,    \                    std::vector<double> &a1,    \                    std::vector<double> &a0,    \                    std::vector<double> &b1,    \                    std::vector<double> &b0){    /* This is not quite as readable, because it involves simplifications     * and some compacting, but it takes care of all the combinations     * the terms can have, whether AP, LP, HP, BP, or BS, odd or even N.     * For example, a2 is 1 all the time except when it's the last/middle     * term for a HP/BS and N is odd, then it's 0.     */    for (int8_t i=0; i<m_vSize; ++i) // all-pole    {        bool midNcheck {i==m_midN && m_oddN};        // a2        a2[i] = !(m_filterCase==CASE_LOWPASS || m_filterCase==CASE_BANDPASS); // odd ones        // a1        if (m_filterCase==CASE_ALLPASS || \                ((m_filterCase==CASE_LOWPASS || m_filterCase==CASE_HIGHPASS) \                    && midNcheck))            a1[i] = (m_filterCase==CASE_ALLPASS ? -2 : 1) * m_real[i];        else            a1[i] = (m_filterCase==CASE_BANDPASS);        // a0        a0[i] = (m_filterCase<CASE_HIGHPASS ? \                    m_real[i]*m_real[i] + m_imag[i]*m_imag[i] : \                    (m_filterCase==CASE_BANDSTOP) * m_wc);        // b1        b1[i] = 2 * m_real[i];        // b0        b0[i] = (midNcheck && m_filterCase<=CASE_HIGHPASS ? m_wc : m_real[i]*m_real[i] + m_imag[i]*m_imag[i]);    }}void Filter::setGain(const bool &normDC, const int8_t &filterType){    m_gain = 1.0;    if (m_filterCase==CASE_BANDPASS)    {        if (filterType==m_types->BUTTERWORTH)        {            for (int8_t i=0; i<m_midN; ++i)                m_gain *= m_bpRatio / m_wscale;            m_gain *= m_gain;            if (m_oddN)                m_gain *= 2 * m_real[m_midN];        }        else    // m_types->CHEBYSHEV:        {            double tmp {m_bpRatio/m_wscale};            for (int8_t i=0; i<m_N; ++i)                m_gain *= tmp;            if (!m_oddN)                m_gain *= sqrt(1 + 1/(m_Kp*m_Kp));            m_gain *= m_Kp / (1<<(m_N-1));        }    }    // normDC=0 means <1 DC gain for filters with passband ripple of even orders.    if (!(normDC || m_oddN) && filterType==m_types->CHEBYSHEV)        m_gain /= sqrt(1 + 1 / (m_Kp*m_Kp));}Butterworth.h#ifndef BUTTERWORTH_H_INCLUDED#define BUTTERWORTH_H_INCLUDED#include Filter.hclass Butterworth: virtual public Filter{public:    Butterworth() {std::cout<<Butterworth\n;}    Butterworth(const double &f0,    \                const double &fp,    \                const double &fs,    \                const double &fc,    \                const double &Ap,    \                const double &As,    \                const short  &N,     \                const int8_t &D);    // calculates the poles    void setPZ(std::vector<double> &real_OUT,   \               std::vector<double> &imag_OUT,   \               const int8_t &n,                 \               const double &wsc,               \               const int8_t &filterCase,        \               const double &f0 = 0,            \               const double &fc = 0);};#endif // BUTTERWORTH_H_INCLUDEDButterworth.cpp#include Butterworth.h#include <cmath>Butterworth::Butterworth(const double &f0,  \                         const double &fp,  \                         const double &fs,  \                         const double &fc,  \                         const double &Ap,  \                         const double &As,  \                         const short  &N,   \                         const int8_t &D): Filter(f0, fp, fs, fc, Ap, As){    // order    if (N)        m_N = N;    else        m_N = ceil( -log(m_Ks / m_Kp) / log(m_freqRatio) );    // frequency scaling    m_wscale = 1;    if (D == DESIGN_D1)        m_wscale *= pow(m_Kp, 1.0/m_N);    else        m_wscale *= pow(m_Ks, 1.0/m_N) * \            (m_filterCase==CASE_LOWPASS || m_filterCase==CASE_BANDPASS ? \            m_fp/m_fs : m_fs/m_fp);    setVars(fc);    // poles    setPZ(m_real, m_imag, m_N, m_wscale, m_filterCase, f0, fc);    storeLPproto();    if (m_filterCase > CASE_LOWPASS)        freqTrans();}// this is used in another file, too, hence the many argumentsvoid Butterworth::setPZ(std::vector<double> &real_OUT,  \                        std::vector<double> &imag_OUT,  \                        const int8_t &n,    \                        const double &wsc,  \                        const int8_t &filterCase,   \                        const double &f0,   \                        const double &fc){    double wcTerm {1/wsc};    int8_t midN {n>>1};    if (f0 && !fc)        wcTerm *= (filterCase<CASE_HIGHPASS ? m_fp : 1/m_fp);    for (int8_t i=0; i<midN; ++i)    {        double iTerm {((i<<1) + 1) * M_PI / (n<<1)};        real_OUT[i] = sin(iTerm) * wcTerm;        imag_OUT[i] = cos(iTerm) * wcTerm;    }    if (n&1)        real_OUT[midN] = wcTerm;}Chebyshev.h#ifndef CHEBYSHEV_H_INCLUDED#define CHEBYSHEV_H_INCLUDED#include Filter.hclass Chebyshev: virtual public Filter{public:    Chebyshev() {std::cout<<Chebyshev\n;}    Chebyshev(const double &f0,     \              const double &fp,     \              const double &fs,     \              const double &fc,     \              const double &Ap,     \              const double &As,     \              const double &Asc,    \              const short  &N,      \              const int8_t &D);};#endif // CHEBYSHEV_H_INCLUDEDChebyshev.cpp#include Chebyshev.h#include <cmath>Chebyshev::Chebyshev(const double &f0,  \                     const double &fp,  \                     const double &fs,  \                     const double &fc,  \                     const double &Ap,  \                     const double &As,  \                     const double &Asc, \                     const short  &N,   \                     const int8_t &D): Filter(f0, fp, fs, fc, Ap, As, Asc){    // first order, ...    if (N)        m_N = N;    else        m_N = ceil(acosh(m_Ks / m_Kp) / acosh(1.0 / m_freqRatio));    // ...then passband ripple factor, since DESIGN_D2 depends on N to calculate it    if (D == DESIGN_D1)        m_Kp  = 1/m_Kp;    else        m_Kp = cosh(acosh(std::max(fp/fs, fs/fp)) * m_N) / m_Ks;    if (Asc)        m_wscale = cosh(acosh(m_Ksc * m_Kp) / m_N);    else        m_wscale = 1;    setVars(fc);    // poles    double wcTerm {(!fc ? sqrt(m_wc) : 1)/m_wscale};    double asinhKp {asinh(m_Kp)/m_N};    double sinhTerm {sinh(asinhKp)};    double coshTerm {cosh(asinhKp)};    for (int8_t i=0; i<m_midN; ++i)    {        double iTerm {((i<<1) + 1) * M_PI / (m_N<<1)};        m_real[i] = sin(iTerm) * sinhTerm * wcTerm;        m_imag[i] = cos(iTerm) * coshTerm * wcTerm;    }    if (m_oddN)        m_real[m_midN] = sinhTerm * wcTerm;    storeLPproto();    if (m_filterCase > CASE_LOWPASS)        freqTrans();}Analysis.h#ifndef ANALYSIS_H_INCLUDED#define ANALYSIS_H_INCLUDED#include Filter.h#include Butterworth.h#include Chebyshev.h#include <complex>#include <memory>class Analysis{private:    // 2nd order transfer function coefficients    std::vector<double> m_a2, m_a1, m_a0, m_b1, m_b0;    // struct to save the arguments for use elsewhere(?)    struct Input    {        double f0, fp, fs, fc, Ap, As, Asc;        bool normDC;        int8_t D, filterType;    };    Input *m_input;                             // save input arguments    std::unique_ptr<Filter> m_ptr;public:    Analysis() {std::cout<<Filter\n;}    /* f0   [Hz]    [0..inf)    Sampling frequency, 0 means analog.     * fp   [Hz]    (0..inf)    Corner frequency / passband bandwidth.     * fs   [Hz]    (0..inf)    Stopband frequency / stopband bandwidth.     * fc   [Hz]    [0..inf)    Center frequency, 0 means AP/LP/HP.     *  - fp, fs, fc should be set to the desired value, be it analog or     *  digital domain; the frequencies are prewarped, internally.     * Ap   [dB]    (0..inf)    Passband ripple (pole-zero filters only).     * As   [dB]    (0..inf)    Stopband attenuation.     * Asc  [dB]    [0..inf)    If set, attenuation @ corner frequency,     *                          otherwise natural attenuation.     * normDC       [0,1]       DC gain normalization (1), or not (0).     * N            [1:inf)     Order.     * D            [1:7]       One of the available designs (Roots::Designs).     *                           1) D1, default design: N is rounded up which     *                          results in Ap@fp match, smaller transition     *                          width, and higher attenuation.     *                           2) D2, passband ripple optimization: matches     *                          As@fs while minimizing the passband ripple, Ap,     *                          resulting in Ap@fp.     * filterType       [1:12]  See enum Types.     */    Analysis(const double &f0,      \             const double &fp,      \             const double &fs,      \             const double &fc,      \             const double &Ap,      \             const double &As,      \             const double &Asc,     \             const bool   &normDC,  \             const short  &N,       \             const int8_t &D,       \             const int8_t &filterType);    // analog transfer function    const std::complex<double> H(const double &x, const double &f0 = 0) const;    // phase    const double phase(const double &x, const double &f0 = 0) const;    // group delay    const double grpDelay(const double &x,  \                          const double &f0, \                          const double &step = 0.001) const;    void printSplane();    void printLaplace();};#endif // ANALYSIS_H_INCLUDEDAnalysis.cpp#include Analysis.hAnalysis::Analysis(const double &f0,    \                   const double &fp,    \                   const double &fs,    \                   const double &fc,    \                   const double &Ap,    \                   const double &As,    \                   const double &Asc,   \                   const bool &normDC,  \                   const short  &N,     \                   const int8_t &D,     \                   const int8_t &filterType): m_input {new Input}{    m_input->f0 = f0;    m_input->fp = fp;    m_input->fs = fs;    m_input->fc = fc;    m_input->Ap = Ap;    m_input->As = As;    m_input->Asc = Asc;    m_input->normDC = normDC;    m_input->D = D;    m_input->filterType = filterType;    // select filter    switch (filterType)    {    case 0://m_ptr->getType()->BUTTERWORTH: // not working #######        m_ptr = std::unique_ptr<Filter>(new Butterworth(f0, fp, fs, fc, Ap, As, N, D));        //Butterworth::setParams(f0, fp, fs, fc, Ap, As, N, D);        break;    case 1://m_ptr->getType()->CHEBYSHEV:   // not working ###########        m_ptr = std::unique_ptr<Filter>(new Chebyshev(f0, fp, fs, fc, Ap, As, Asc, N, D));        //Chebyshev::setParams(f0, fp, fs, fc, Ap, As, Asc, N, D);        break;    }    m_a2.resize(m_ptr->getVsize());    m_a1.resize(m_ptr->getVsize());    m_a0.resize(m_ptr->getVsize());    m_b1.resize(m_ptr->getVsize());    m_b0.resize(m_ptr->getVsize());    m_ptr->setLaplace(m_a2, m_a1, m_a0, m_b1, m_b0);    m_ptr->setGain(normDC, filterType);}// Transfer functionconst std::complex<double> Analysis::H(const double &x, const double &f0) const{    std::complex<double> num {1.0, 0.0};    std::complex<double> den {1.0, 0.0};    std::complex<double> jw {0, x};    for (int8_t i=0; i<m_ptr->getVsize(); ++i)    {        num *= (m_a2[i]*jw + m_a1[i])*jw + m_a0[i];        den *= (jw + m_b1[i])*jw + m_b0[i];    }    return m_ptr->getGain()*num/den;}const double Analysis::phase(const double &x, const double &f0) const{    std::complex<double> h {H(x, f0)};    return atan2(h.imag(), h.real());}const double Analysis::grpDelay(const double &x,    \                              const double &f0,   \                              const double &step) const{    return (phase(x, f0) - phase(x+step, f0))*step;}void Analysis::printSplane(){    std::cout << N= << m_ptr->getN() << , wsc= << m_ptr->getWscale() << '\n';    std::cout << poles:\n;    for (int8_t i=0; i<m_ptr->getVsize(); ++i)        std::cout << -m_ptr->getReal(i) <<  \u00b1 j << m_ptr->getImag(i) << '\n';    std::cout << '\n';}void Analysis::printLaplace(){    std::cout << N= << m_ptr->getN() << , wsc= << m_ptr->getWscale() << '\n' << Laplace=;    for (int8_t i=0; i<m_ptr->getVsize(); ++i)    {        bool a2 {m_a2[i] > 0};        bool a1 {m_a1[i] > 0};        bool a0 {m_a0[i] > 0};        std::cout << ( \                  << (a2            ? (s/w)^2                         : )   \                  << (a2&&(a1||a0)  ? +                               : )   \                  << (a1            ? s/w* + std::to_string(m_a1[i])  : )   \                  << (a1&&a0        ? +                               : )   \                  << (a0            ? std::to_string(m_a0[i])           : )   \                  << )/((s/w)^2+s/w* << m_b1[i] << + << m_b0[i] << )*;    }    std::cout << m_ptr->getGain() << \n\n;}main.cpp#include Analysis.h#include <string>       // std:stod, std::stoiint main(int argc, char *argv[]){    double  f0  {argc<2     ? 0     : std::stod(argv[1])};    double  fp  {argc<3     ? 1     : std::stod(argv[2])};    double  fs  {argc<4     ? 2     : std::stod(argv[3])};    double  fc  {argc<5     ? 0     : std::stod(argv[4])};    double  Ap  {argc<6   ? 0.91515 : std::stod(argv[5])};    double  As  {argc<7     ? 20    : std::stod(argv[6])};    double  Asc {argc<8     ? 0     : std::stod(argv[7])};    bool normDC {argc<9     ? 0     : static_cast<bool>(std::stoi(argv[8]))};    short   N   {argc<10    ? 0     : static_cast<short>(std::stoi(argv[9]))};    int8_t  D   {argc<11    ? 1     : static_cast<int8_t>(std::stoi(argv[10]))};    int8_t fType {argc<12   ? 1     : static_cast<int8_t>(std::stoi(argv[11]))};    Analysis *a {new Analysis};    int Nx {f0 ? 1024 : 1000};    double minim {f0 ? 0 : 0.01};    double maxim {f0 ? f0/2 : 100};    for (short i=0; i<=1; ++i)    {        a = new Analysis(f0, fp, fs, fc, Ap, As, Asc, normDC, N, D, i);        a->printSplane();        a->printLaplace();    }    return 0;}The way I thought the whole thing was to have a storage class, Filter, which holds all the common variables and some functions, since all filters will be using the same variables (more or less). Then, Butterworth is-a Filter, Chebyshev is-a Filter, so it makes sense to use inheritance. Then, Analysis calculates, based on Filter's variables, the transfer function, coefficients, evaluates the frequency response, phase, group delay. I thought Butterworth has-a transfer function, so Analysis uses composition. The std::unique_ptr serves to be able to select one filter at a time. In main(), calling is done through Analysis *a, which would make things simpler for the GUI that I intend to make..If you made it this far, the small history is that in the beginning I used inheritance for Analysis, too, it worked, but someone said that the way I made it is awful, in that it uses a ton of inheritance (it did) that's badly made. It was suggested I tried composition, or namespaces, it didn't work as I wanted, or at all, then, after trials and errors, I mixed it up with both inheritance and composition, as it is now.Does the monster above have any value? Did I set a new record on bad coding? Is it even sane to ask, or think the way I did? If, by any chance, all's not that bad, is this a good direction I took?
Simple home-brew filter design for a future GUI
c++;beginner;object oriented
I think you're on the right track but I'm not sure you need to place the Filter inside the Analysis.Inheritance is perfect for the different types of filters. You're right, a Chebychev 'is a kind of' Filter, so this works.However at this point it's probably okay to stop. A filter analyser is probably entirely separate from a Filter. Think of it in the real world like an oscilloscope or other measuring device. It doesn't have any filters inside it (composition), nor is it a kind of filter (inheritance). I think it's okay to just leave these separate, and in your running program, just instantiate a Filter and an Analyser, then get to work. The analyser can just take a Filter as an calling argument.Since you seem to be aiming for generality and future usability, think of other use cases of a Filter. These also suggest separating the Filter from the Analyser and perhaps other filter related things. Maybe, a tool that applies one of your filters to a WAV file? Or perhaps the filters might end up in a audio plugin, processing blocks of samples in realtime from a soundcard input?Take a step back and think how you might use it. It often helps to think build from the outside in. Start inside your main function and write a line that calls in the 'perfect' and easiest way. Looking at things this way can really help structure things and you'll write more direct code.So with this said, and without knowing your needs (I might be wrong) but I hope this will help you find your way.Here's a sketch of what I mean by working from the outside in...//==============================================================================// Your command line programint main(){    //...    std::unique_ptr<flt::Filter> filter {makeFilterFromCommandLineArguments()};    flt::Analyser fa {filter};    fa.printSplane();    fa.printLaplace();    //...    // (let smart pointer handle cleanup of Filter)}//==============================================================================// Say you have an offline WAV file filtering tool with analysis in the future,// How would you want to use it?// in some class WavFilterstd::vector<float> apply(flt::Filter* filterToApply,                         const std::vector<float>& signal);// in some class WavWritervoid writeWavFileToDisk(const std::vector<float>& signal);void use(){    std::vector<float> inVector {makeVectorFromWavFile()}; // or adapt for stereo    flt::Chebyshev   chebFilt {/* set all the params */};    flt::Butterworth buttFilt {/* set all the params */};    flt::Analyser Analyser;    Analyser.printSplane(&chebFilt);    Analyser.printSplane(&buttFilt);    flt::WavFilter wavFilter;    std::vector<float> outFileCheb {wavFilter.apply(&chebFilt, inVector)};    std::vector<float> outFileButt {wavFilter.apply(&buttFilt, inVector)};    wavFilter.writeWavFileToDisk(outFileCheb);    wavFilter.writeWavFileToDisk(outFileButt);}//==============================================================================// Or a realtime audio program that applies your filter to incoming blocks of// samples from a soundcard...std::unique_ptr<flt::Filter> filter {nullptr};void setup(const flt::FilterParamSet& params){    flt::ChebyshevZ chebFiltZDomain {params};   // sorry terrible name! but                                                // clearly you'll be applying a                                                // digital filter here}// This gets called very often, for a block size of 128 samples at 44.1kHz// that's approx every 3ms! So you want this code to be non-blocking and// quickvoid processBlock(Buffer& inOutBlock){    chebFiltZDomain.applyFilterToBlock(inOutBlock);}P.s. a few small pointsI think a namespace is a good idea. It took me a while to get used to looking at, and typing fully qualified names like flt::Analyser but when you get used to it, it's very hard to go back. Much more organised and frees you from writing names that repeat the word 'Filter' over and over. Namespaces allow you to group things where composition or inheritance isn't appropriate.Nice use of const all round but no need to do const double&, const int& etc. Just pass and return these by value i.e. double someFunction(double Fs, int filterOrder). It's as fast if not faster to copy a few doubles or ints, and a returning by value means you can't change the data anyway.Try and use pointers less. Only where unavoidable. They are more error prone to write for and harder to debug. For inherited classes they make sense. For very large objects like a soundfile they also make sense (so as to access the heap). They also make perfect sense for a situation where 'nothing' is a valid state. But for most everything else, the stack works very nicely :)Consider making a flt::Parameters type that can pass in to functions as it's easy to forget the order of parameters see here CppCoreGuidelines you could do some little type aliases tooI don't see much in the way of error handling? It's better to think about that early and write as you go, also, your own self, in 6 months or a year would thank you! Exceptions are probably the way to go (but not in the realtime case as exceptions can be unpredictable in terms of time). And/or consider filling your code with assert()s to check arguments and other things in debug mode. (But remember that assert() is only for you the programmer during debugging, not for the end user).In a switch you should always have a default case even if it just breaks out into the debugger with assert(). Falling through the bottom of a switch can be a tough issue to debug.You don't need to split lines with \ just go to the next line (nice use of vertical alignment BTW)Hope this helps!!!