Skip to content

Commit

Permalink
fix #271723: implement filters in Zerberus
Browse files Browse the repository at this point in the history
Implement cutoff, fil_veltrack, fil_type (lpf_2p and hpf_2p).
  • Loading branch information
anatoly-os committed Jun 6, 2018
1 parent 1ac1f1a commit b7ae054
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 21 deletions.
47 changes: 47 additions & 0 deletions zerberus/sfz.cpp
Expand Up @@ -94,6 +94,14 @@ struct SfzRegion {
long long offset; // [0, 4Gb) or [0, 4294967295]
float group_volume; // [-144 to 6] (dB)

//filters
bool isCutoffDefined;
float cutoff; //[0, sampleRate / 2] Hz
int fil_keytrack; //[0, 1200] cents
int fil_keycenter; //[0, 127]
int fil_veltrack; //[-9600, 9600] cents
FilterType fil_type;

void init(const QString&);
bool isEmpty() const { return sample.isEmpty(); }
int readKey(const QString&, SfzControl c) const;
Expand Down Expand Up @@ -158,6 +166,12 @@ void SfzRegion::init(const QString& _path)
pan = 0;
offset = 0;
group_volume = 0.0f;
isCutoffDefined = false;
cutoff = 0;
fil_keytrack = 0;
fil_keycenter = 60;
fil_veltrack = 0;
fil_type = FilterType::lpf_2p;
}

//---------------------------------------------------------
Expand Down Expand Up @@ -215,6 +229,13 @@ void SfzRegion::setZone(Zone* z) const
z->delay = delay * 1000; //convert seconds from sfz to ms for computations
z->pan = pan;
z->group_volume = pow(10.0, group_volume / 20.0); //dB -> volume multiplier

z->isCutoffDefined = isCutoffDefined;
z->cutoff = cutoff;
z->fil_keycenter = fil_keycenter;
z->fil_keytrack = fil_keytrack;
z->fil_veltrack = fil_veltrack;
z->fil_type = fil_type;
}

//---------------------------------------------------------
Expand Down Expand Up @@ -324,6 +345,20 @@ static void readDouble(const QString& data, double* val)
*val = d;
}

static void readFilterType(const QString& data, FilterType& filType)
{
if (data == "lpf_1p")
filType = FilterType::lpf_1p;
else if (data == "lpf_2p")
filType = FilterType::lpf_2p;
else if (data == "hpf_1p")
filType = FilterType::hpf_1p;
else if (data == "hpf_2p")
filType = FilterType::hpf_2p;
else
qDebug("SfzRegion: not supported fil_type value: %s", qPrintable(data));
}

//---------------------------------------------------------
// readOp
//---------------------------------------------------------
Expand Down Expand Up @@ -510,6 +545,18 @@ void SfzRegion::readOp(const QString& b, const QString& data, SfzControl &c)
readLongLong(opcode_data, offset);
else if (opcode == "group_volume")
readFloat(opcode_data, group_volume);
else if (opcode == "cutoff") {
isCutoffDefined = true;
readFloat(opcode_data, cutoff);
}
else if (opcode == "fil_keycenter")
fil_keycenter = i;
else if (opcode == "fil_keytrack")
fil_keytrack = i;
else if (opcode == "fil_veltrack")
fil_veltrack = i;
else if (opcode == "fil_type")
readFilterType(opcode_data, fil_type);
else
qDebug("SfzRegion: unknown opcode <%s>", qPrintable(opcode));
}
Expand Down
65 changes: 44 additions & 21 deletions zerberus/voice.cpp
Expand Up @@ -145,7 +145,16 @@ void Voice::start(Channel* c, int key, int v, const Zone* zone, double durSinceN
targetcents = z->keyBase * 100;
phaseIncr.set(_zerberus->ct2hz(targetcents) * sr/_zerberus->ct2hz(z->keyBase * 100.0));

fres = 13500.0;
fres = _zerberus->ct2hz(13500.0);
if (z->isCutoffDefined) {
//calculate current cutoff value
float cutoffHz = z->cutoff;
//Formula for converting the interval frequency ratio f2 / f1 to cents (c or ¢).
//¢ or c = 1200 × log2 (f2 / f1)
cutoffHz *= pow(2.0, _velocity / 127.0f * z->fil_veltrack / 1200.0);
fres = cutoffHz;
}

last_fres = -1.0;
qreal GEN_FILTERQ = 100.0; // 0 - 960
qreal q_db = GEN_FILTERQ / 10.0f - 3.01f;
Expand Down Expand Up @@ -243,11 +252,30 @@ void Voice::updateFilter(float _fres)
* b1=(1.-cos_coeff)*a0_inv*voice->filter_gain;
* b2=(1.-cos_coeff)*a0_inv*0.5*voice->filter_gain; */

float a1_temp = -2.0f * cos_coeff * a0_inv;
float a2_temp = (1.0f - alpha_coeff) * a0_inv;
float b1_temp = (1.0f - cos_coeff) * a0_inv * filter_gain;
// both b0 -and- b2
float b02_temp = b1_temp * 0.5f;
float a1_temp = 0.f;
float a2_temp = 0.f;
float b1_temp = 0.f;
float b02_temp = 0.f;
switch (z->fil_type) {
case FilterType::lpf_2p: {
a1_temp = -2.0f * cos_coeff * a0_inv;
a2_temp = (1.0f - alpha_coeff) * a0_inv;
b1_temp = (1.0f - cos_coeff) * a0_inv * filter_gain;
// both b0 -and- b2
b02_temp = b1_temp * 0.5f;
break;
}
case FilterType::hpf_2p: {
a1_temp = -2.0f * cos_coeff * a0_inv;
a2_temp = (1.0f - alpha_coeff) * a0_inv;
b1_temp = -(1.0f + cos_coeff) * a0_inv * filter_gain;
// both b0 -and- b2
b02_temp = -b1_temp * 0.5f;
break;
}
default:
qWarning() << "fil_type is not implemented: " << (int)z->fil_type;
}

if (filter_startup) {
/* The filter is calculated, because the voice was started up.
Expand Down Expand Up @@ -310,22 +338,17 @@ void Voice::updateEnvelopes() {

void Voice::process(int frames, float* p)
{
float modlfo_to_fc = 0.0;
float modenv_to_fc = 0.0;

float _fres = _zerberus->ct2hz(fres
+ modlfo_val * modlfo_to_fc
+ modenv_val * modenv_to_fc);

float adaptedFrequency = fres;
int sr = _zerberus->sampleRate();
if (_fres > 0.45f * sr)
_fres = 0.45f * sr;
else if (_fres < 5.f)
_fres = 5.f;

if ((fabs(_fres - last_fres) > 0.01f)) {
updateFilter(_fres);
last_fres = _fres;
if (adaptedFrequency > 0.45f * sr)
adaptedFrequency = 0.45f * sr;
else if (adaptedFrequency < 5.f)
adaptedFrequency = 5.f;

bool freqWasUpdated = fabs(adaptedFrequency - last_fres) > 0.01f;
if (freqWasUpdated) {
updateFilter(adaptedFrequency);
last_fres = adaptedFrequency;
}

const float opcodePanLeftGain = 1.f - fmax(0.0f, z->pan / 100.0); //[0, 1]
Expand Down
15 changes: 15 additions & 0 deletions zerberus/zone.h
Expand Up @@ -40,6 +40,13 @@ enum class OffMode : char {
FAST, NORMAL
};

enum class FilterType {
lpf_2p, //default, double-pole low-pass filter
lpf_1p, //single-pole low-pass filter
hpf_2p, //double-pole high-pass filter
hpf_1p //single-pole high-pass filter
};

//---------------------------------------------------------
// Zone
//---------------------------------------------------------
Expand Down Expand Up @@ -80,6 +87,14 @@ struct Zone {
int pan = 0;
float group_volume = 1.0;

//filters
bool isCutoffDefined;
float cutoff; //[0, sampleRate / 2] Hz
int fil_keytrack; //[0, 1200] cents
int fil_keycenter; //[0, 127]
int fil_veltrack; //[-9600, 9600] cents
FilterType fil_type = FilterType::lpf_2p;

Trigger trigger = Trigger::ATTACK;
LoopMode loopMode = LoopMode::NO_LOOP;
OffMode offMode = OffMode::FAST;
Expand Down

0 comments on commit b7ae054

Please sign in to comment.