diff --git a/zerberus/sfz.cpp b/zerberus/sfz.cpp index 5b82407e301c..bda0a7894bd5 100644 --- a/zerberus/sfz.cpp +++ b/zerberus/sfz.cpp @@ -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; @@ -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; } //--------------------------------------------------------- @@ -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; } //--------------------------------------------------------- @@ -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 //--------------------------------------------------------- @@ -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)); } diff --git a/zerberus/voice.cpp b/zerberus/voice.cpp index 05f2d478c7f5..31551e817157 100644 --- a/zerberus/voice.cpp +++ b/zerberus/voice.cpp @@ -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; @@ -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. @@ -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] diff --git a/zerberus/zone.h b/zerberus/zone.h index 4017aa38eb4f..a59ebf78cad3 100644 --- a/zerberus/zone.h +++ b/zerberus/zone.h @@ -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 //--------------------------------------------------------- @@ -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;