High Pass Filters
Digital Biquad Filtering
Brief:
A second-order biquad high-pass filter attenuates frequencies below a specified cutoff while allowing higher frequencies to pass through, using a combination of two poles and two zeros to shape the frequency response. It is implemented using a difference equation with feedback and feedforward coefficients, offering precise control over gain, cutoff frequency, and resonance (Q factor).
\(f_{0}\) = 1000
\(Q\) = 0.707
Formulae:
In order to construct a biquad high-pass filter, the sample rate
, cutoff frequency
, and Q-factor
need to be provided. From those, the coefficients for the underlying filter can be calculated:
First, some intermediate values are calculated:
- \(ω_{0} = 2π{f_{0} \over F_{s}}\)
- \(α = {sin(ω_{0}) \over {2Q}}\)
Where \(f_{0}\) is the cutoff frequency, \(F_{s}\) is the sample rate, and \(Q\) is the Q-factor.
Then, the filter coefficients can be calculated:
- \(b_{0} = {{1 + cos(ω_{0})}\over{2}}\)
- \(b_{1} = -(1 + cos(ω_{0}))\)
- \(b_{2} = {{1 + cos(ω_{0})}\over{2}}\)
- \(a_{0} = 1 + α\)
- \(a_{1} = -2cos(ω_{0})\)
- \(a_{2} = 1 - α\)
C++ Implementation:
Requires: DigitalBiquadFilter.h
/// HighPassFilter.h
/**
Copyright © 2025 Alex Parisi
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef HIGH_PASS_FILTER_H
#define HIGH_PASS_FILTER_H
#include "DigitalBiquadFilter.h"
/**
* @brief High Pass Filter
* @details A high pass filter is a filter that passes signals with a
* frequency higher than a certain cutoff frequency and attenuates signals
* with frequencies lower than the cutoff frequency.
* @param cutoff The cutoff frequency of the filter
* @param sampleRate The sample rate of the input signal
* @param qFactor The quality factor of the filter, default value is
* 0.7071067811865476, or 1/sqrt(2)
* @return A high pass filter object
* @see DigitalBiquadFilter
*/
template<std::floating_point T, size_t blockSize = 0>
class HighPassFilter {
public:
static auto create(double cutoff, int sampleRate,
double qFactor = 0.7071067811865476)
-> std::optional<HighPassFilter> {
if (sampleRate <= 0) {
LOG_E("HighPassFilter", "Error creating HighPassFilter object: "
"sample rate must be greater than zero.");
return std::nullopt;
}
if (cutoff > sampleRate / 2.0) {
LOG_E("HighPassFilter",
"Error creating HighPassFilter object: cutoff frequency must "
"be less than half the sample rate (Nyquist frequency).");
return std::nullopt;
}
if (auto obj = HighPassFilter(cutoff, sampleRate, qFactor);
!obj.m_filter.has_value()) {
LOG_E("HighPassFilter",
"Error creating HighPassFilter object: DigitalBiquadFilter "
"object could not be created.");
return std::nullopt;
}
return HighPassFilter(cutoff, sampleRate, qFactor);
}
/**
* @brief Process a sample
* @details Processes a single sample of audio data
* @param sample The input sample
*/
auto process(const T &sample) -> float { return m_filter->process(sample); }
/**
* @brief Process a block of samples
* @details Processes a block of samples of audio data
* @param samples A pointer to the block of samples. The samples are edited
* in-place. This assumes that the pointer has enough memory allocated to
* pull out blockSize samples and is inherently unsafe. For block,
* processing, it's safer to use the block process methods with either a
* std::vector or std::array.
*/
auto process(T *samples) -> void {
if (blockSize <= 0 || samples == nullptr) {
LOG_E("HighPassFilter",
"Error processing block of samples: blockSize must be "
"greater than zero and samples pointer cannot be null");
return;
}
m_filter->process(samples);
}
/**
* @brief Process a block of samples
* @details Processes a block of samples of audio data. Since this method
* uses a std::vector as input, we don't need to rely on blockSize being
* passed in.
* @param samples A vector of samples
* @return A vector of processed samples
*/
auto process(std::vector<double> &samples) -> void {
if (samples.empty()) {
LOG_E("HighPassFilter",
"Error processing block of samples: samples vector is empty");
return;
}
m_filter->process(samples);
}
/**
* @brief Process a block of samples
* @details Processes a block of samples of audio data
* @param samples An array of samples
* @return An array of processed samples
*/
auto process(const std::array<double, blockSize> &samples) -> void {
if (blockSize <= 0) {
LOG_E("HighPassFilter", "Error processing block of samples: "
"blockSize must be greater than zero");
return;
}
m_filter->process(samples);
}
/**
* @brief Set the cutoff frequency of the filter
* @param cutoff The cutoff frequency of the filter
*/
auto set_cutoff(const double cutoff) -> void {
m_cutoff = cutoff;
Coefficients<T> newCoefficients =
calculate_coefficients(m_cutoff, m_sampleRate, m_qFactor);
m_filter->set_coefficients(newCoefficients);
}
/**
* @brief Get the cutoff frequency of the filter
* @return The cutoff frequency of the filter
*/
[[nodiscard]] auto get_cutoff() const -> double { return m_cutoff; }
/**
* @brief Set the sample rate of the input signal
* @param sampleRate The sample rate of the input signal
*/
auto set_sample_rate(const int sampleRate) -> void {
m_sampleRate = sampleRate;
Coefficients<T> newCoefficients =
calculate_coefficients(m_cutoff, m_sampleRate, m_qFactor);
m_filter->set_coefficients(newCoefficients);
}
/**
* @brief Get the sample rate of the input signal
* @return The sample rate of the input signal
*/
[[nodiscard]] auto get_sample_rate() const -> int { return m_sampleRate; }
/**
* @brief Set the quality factor of the filter
* @param qFactor The quality factor of the filter
*/
auto set_q_factor(const double qFactor) -> void {
m_qFactor = qFactor;
Coefficients<T> newCoefficients =
calculate_coefficients(m_cutoff, m_sampleRate, m_qFactor);
m_filter->set_coefficients(newCoefficients);
}
/**
* @brief Get the quality factor of the filter
* @return The quality factor of the filter
*/
[[nodiscard]] auto get_q_factor() const -> double { return m_qFactor; }
/**
* @brief Set the bandwidth of the filter
* @param bandwidth The bandwidth of the filter
*/
auto set_bandwidth(const float bandwidth) -> void {
const double Q =
1.0 / (2.0 * std::sinh(bandwidth * std::log10(2.0) / 2.0));
set_q_factor(Q);
}
/**
* @brief Get the bandwidth of the filter
* @return The bandwidth of the filter
*/
[[nodiscard]] auto get_bandwidth() const -> double {
const double bandwidth =
2.0 * std::asinh(1.0 / (2.0 * m_qFactor)) / std::log10(2.0);
return bandwidth;
}
private:
/**
* @brief Private Constructor
* @details Initializes the filter with parameters
* @param cutoff The cutoff frequency of the filter
* @param sampleRate The sample rate of the input signal
* @param qFactor The quality factor of the filter
*/
HighPassFilter(const double cutoff, const int sampleRate,
const double qFactor) :
m_cutoff(cutoff), m_sampleRate(sampleRate), m_qFactor(qFactor) {
Coefficients<T> coefficients =
calculate_coefficients(cutoff, sampleRate, qFactor);
m_filter = DigitalBiquadFilter<T, blockSize>::create(coefficients);
}
/**
* @brief Calculate the filter coefficients
* @details Calculates the filter coefficients based on the cutoff
* frequency, sample rate, and quality factor
*/
[[nodiscard]] static auto calculate_coefficients(const double cutoff,
const int sampleRate,
const double qFactor)
-> Coefficients<T> {
const T w0 = 2.0 * M_PI * cutoff / sampleRate;
const T cos_w0 = std::cos(w0);
const T alpha = std::sin(w0) / (2.0 * qFactor);
const T b1 = -(1.0 + cos_w0);
const T b0 = -b1 / 2.0;
const T b2 = b0;
const T a0 = 1.0 + alpha;
const T a1 = -2.0 * cos_w0;
const T a2 = 1.0 - alpha;
return Coefficients{b0, b1, b2, a0, a1, a2};
}
std::optional<DigitalBiquadFilter<T, blockSize>> m_filter;
double m_cutoff;
int m_sampleRate;
double m_qFactor;
};
#endif // HIGH_PASS_FILTER_H
Notes:
- Since Q-factor is a unit-less value and is a little hard to quantify, sometimes it is easier to provide bandwidth instead. Bandwidth can be converted to the Q-factor by using the formula:
- \(Q = {1 \over {2sinh(BW ⋅ {log_{10}(2) \over 2})}}\)
- There is a concept applied to the templated coefficients struct, state struct, and filter class:
std::floating_point
. This ensures that theHighPassFilter
class can only be templated with afloat
ordouble
type. - To reduce the chance of encountering quantization noise, it's recommended to always template the
HighPassFilter
class with adouble
type.