You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
128 lines
3.2 KiB
128 lines
3.2 KiB
#ifndef __SVF_H__
|
|
#define __SVF_H__
|
|
|
|
/* CEM3320/Oberheim/Phrophet-style filter as described here: https://arxiv.org/pdf/2111.05592.pdf */
|
|
|
|
#include <cmath>
|
|
#include <cfloat>
|
|
#include <iostream>
|
|
#include <algorithm>
|
|
#include <functional>
|
|
|
|
#include "../globals.h"
|
|
#include "util.h"
|
|
|
|
#define FILTER_OVERSAMPLE 4
|
|
#define FILTER_K_SCALE (2 * CV_FREQ_MIN / (FILTER_OVERSAMPLE * SAMPLE_RATE))
|
|
#define FILTER_CV_MAX 9.25 // MIDI note "135" (19912.126958213178287 Hz)
|
|
|
|
inline float cvToK(float cv) {
|
|
return tanf((float) M_PI_2 * std::min(0.5f, std::min(1.f / FILTER_OVERSAMPLE, (float) FILTER_K_SCALE * exp2f(cv))));
|
|
}
|
|
|
|
class SVF12 {
|
|
public:
|
|
typedef struct {
|
|
float lp; // low-pass
|
|
float bp; // band-pass
|
|
float hp; // high-pass
|
|
} Output;
|
|
|
|
protected:
|
|
float as1, as2;
|
|
|
|
public:
|
|
SVF12() : as1(0), as2(0) {}
|
|
|
|
inline Output tick(float in, float kK, float kQ) {
|
|
Output out;
|
|
float kdiv = 1 + kK/kQ + kK*kK;
|
|
out.hp = (in - (1/kQ + kK) * as1 - as2) / kdiv;
|
|
float au = out.hp * kK;
|
|
out.bp = au + as1;
|
|
as1 = au + out.bp;
|
|
au = out.bp * kK;
|
|
out.lp = au + as2;
|
|
as2 = au + out.lp;
|
|
return out;
|
|
}
|
|
};
|
|
|
|
class Oversampler {
|
|
private:
|
|
float kK, kQ = (float) M_SQRT1_2;
|
|
SVF12 aa1, aa2;
|
|
|
|
public:
|
|
Oversampler() : kK(tanf((float) M_PI_2 * 1.0f / FILTER_OVERSAMPLE)) {}
|
|
|
|
inline float tick(float in, std::function<float(float)> process) {
|
|
if(FILTER_OVERSAMPLE < 2) {
|
|
return process(in);
|
|
} else {
|
|
// float out = aa2.tick(aa1.tick(times * process(in), kK, kQ).lp, kK, kQ).lp;
|
|
// for(int i = 1; i < times; ++i) {
|
|
// aa2.tick(aa1.tick(process(0), kK, kQ).lp, kK, kQ);
|
|
// }
|
|
float out = process(aa2.tick(aa1.tick(FILTER_OVERSAMPLE * in, kK, kQ).lp, kK, kQ).lp);
|
|
for(int i = 1; i < FILTER_OVERSAMPLE; ++i) {
|
|
process(aa2.tick(aa1.tick(0, kK, kQ).lp, kK, kQ).lp);
|
|
}
|
|
|
|
return out;
|
|
}
|
|
}
|
|
};
|
|
|
|
// 12 and 24 dB/oct
|
|
class Filter {
|
|
public:
|
|
enum Type {
|
|
TYPE_LP = 0,
|
|
TYPE_BP,
|
|
TYPE_HP
|
|
};
|
|
|
|
enum Slope {
|
|
SLOPE_24 = 0,
|
|
SLOPE_12,
|
|
};
|
|
|
|
typedef struct {
|
|
float freq;
|
|
float res;
|
|
Type type;
|
|
Slope slope;
|
|
} Settings;
|
|
|
|
private:
|
|
Settings const * settings;
|
|
SVF12 fltA, fltB;
|
|
Oversampler os;
|
|
|
|
public:
|
|
void assign(Settings const * settings);
|
|
|
|
inline float tick(float in, float freqMod) {
|
|
float kK = cvToK(settings->freq + freqMod);
|
|
float kQ = (float) M_SQRT1_2 + settings->res;
|
|
|
|
return os.tick(in, [this, kK, kQ](float oin) {
|
|
SVF12::Output outA = fltA.tick(oin, kK, kQ);
|
|
|
|
switch(settings->type) {
|
|
case TYPE_LP:
|
|
return settings->slope == SLOPE_24 ? fltB.tick(outA.lp, kK, kQ).lp : outA.lp;
|
|
case TYPE_BP:
|
|
return settings->slope == SLOPE_24 ? fltB.tick(outA.bp, kK, kQ).bp : outA.bp;
|
|
case TYPE_HP:
|
|
return settings->slope == SLOPE_24 ? fltB.tick(outA.hp, kK, kQ).hp : outA.hp;
|
|
default:
|
|
return oin;
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
|
|
#endif |