Implementing DH_AllpassFilter in DSP: Code Examples and PerformanceAn all-pass filter passes all input frequencies with equal gain while altering the phase response. A delay–half (DH) all-pass filter (DH_AllpassFilter) is a common building block in digital signal processing (DSP) used for phase correction, artificial reverberation, phase-based interpolation, and fractional delay implementations. This article explains the theory, presents practical C++ and Python implementations, shows how to use the filter in common DSP tasks, and analyzes performance implications and optimization strategies.
What is a DH All-pass Filter?
An all-pass filter has a transfer function H(z) satisfying |H(e^{jω})| = 1 for all ω. The DH structure usually refers to a particular implementation topology where a unit delay and feedforward/feedback coefficients are arranged to achieve the all-pass property while providing an adjustable phase shift and fractional delay.
A common first-order all-pass filter in z-domain: H(z) = (a + z^{-1}) / (1 + a z^{-1}), where |a| < 1 ensures stability.
The DH_AllpassFilter extends this idea to provide a delay-plus-allpass behaviour, often used to approximate fractional delays: H(z) = z^{-N} * (b0 + b1 z^{-1} + … ) / (1 + a1 z^{-1} + …)
For practical DSP, two key properties are exploited:
- Unity magnitude: avoids changing spectral magnitude while reshaping phase.
- Adjustable group delay: useful for aligning channels, implementing fractional delays, and creating dense echo networks (reverb).
Design Variants and Use Cases
- First-order DH all-pass: simple, low-cost phase shifter (a single coefficient).
- Higher-order cascade of first-order sections: achieves larger and more flexible phase responses while maintaining numerical stability.
- Lattice or Schur-form implementations: provide robust numerical stability for higher-order sections.
- Fractional delay approximations: combining an integer delay with an all-pass section to approximate sub-sample delays.
- Reverb/diffusion networks: many DH all-pass filters in series produce dense phase scattering.
Typical use cases:
- Stereo phase alignment and channel matching.
- Fractional delay for sample-rate conversion or delay-line tuning.
- Building blocks in feedback delay networks (FDNs) for artificial reverbs.
- Phase-correcting filters in measurement systems.
Basic Theory (Concise)
First-order all-pass (canonical): H(z) = (a + z^{-1}) / (1 + a z^{-1})
Group delay τ(ω) = -d arg(H(e^{jω}))/dω. By selecting a appropriately, the filter produces desired phase slope around frequency bands.
Cascading N first-order sections multiplies their phase responses, allowing more complex delay/phase shaping while preserving unity magnitude.
Reference Implementations
Below are practical implementations: a straightforward, readable C++ real-time-friendly implementation, and a Python reference for experimentation. Both implement a cascade of first-order all-pass sections with optional integer delay (DH behavior: integer delay + all-pass section(s)).
C++ (real-time, single-sample process)
- Single-precision floating point (float) for speed.
- Circular buffer for integer delay.
- Per-section state (past sample).
- No dynamic allocations in process path.
// DH_AllpassFilter.h #pragma once #include <vector> #include <cmath> class DH_AllpassFilter { public: // aCoeffs: vector of "a" coefficients for each first-order all-pass section // delaySamples: integer delay in samples (>= 0) DH_AllpassFilter(const std::vector<float>& aCoeffs, int delaySamples = 0); // Process one sample (mono) float processSample(float input); // Reset internal state void reset(); private: std::vector<float> a; // all-pass coefficients std::vector<float> state; // per-section previous output (y[n-1]) std::vector<float> delayBuf; // circular buffer for integer delay int delayWriteIdx; int delaySize; };
// DH_AllpassFilter.cpp #include "DH_AllpassFilter.h" DH_AllpassFilter::DH_AllpassFilter(const std::vector<float>& aCoeffs, int delaySamples) : a(aCoeffs), state(aCoeffs.size(), 0.0f), delaySize(std::max(0, delaySamples)), delayBuf(delaySize > 0 ? std::vector<float>(delaySize, 0.0f) : std::vector<float>()), delayWriteIdx(0) {} void DH_AllpassFilter::reset() { std::fill(state.begin(), state.end(), 0.0f); if (delaySize > 0) std::fill(delayBuf.begin(), delayBuf.end(), 0.0f); delayWriteIdx = 0; } // Single first-order all-pass section processing (in-place) static inline float allpass1_process(float x, float a, float& s) { // y[n] = -a*x + s // s gets updated to x + a*y[n] float y = -a * x + s; s = x + a * y; return y; } float DH_AllpassFilter::processSample(float input) { float x = input; // Optional integer delay (DH part) if (delaySize > 0) { float delayed = delayBuf[delayWriteIdx]; delayBuf[delayWriteIdx] = x; delayWriteIdx = (delayWriteIdx + 1) % delaySize; x = delayed; } // Cascade of first-order all-pass sections for (size_t i = 0; i < a.size(); ++i) { x = allpass1_process(x, a[i], state[i]); } return x; }
Notes:
- The allpass1_process uses the direct form that keeps only one state variable per section and is efficient and numerically stable for |a| < 1.
- For stereo, instantiate two independent instances or process per-channel state.
Python (reference, not real-time)
This implementation is convenient for testing, plotting phase/group-delay, and exploring coefficient effects.
”`python
dh_allpass.py
import numpy as np
class DH_AllpassFilter:
def __init__(self, a_coeffs, delay_samples=0): self.a = np.array(a_coeffs, dtype=float) self.states = np.zeros_like(self.a) self.delay_samples = int(max(0, delay_samples)) if self.delay_samples > 0: self.delay_buf = np.zeros(self.delay_samples) self.write_idx = 0 def reset(self): self.states.fill(0.0) if self.delay_samples > 0: self.delay_buf.fill(0.0) self.write_idx = 0 def process_sample(self, x): # integer delay if self.delay_samples > 0: delayed = self.delay_buf[self.write_idx] self.delay_buf[self.write_idx] = x self.write_idx = (self.write_idx + 1) % self.delay_samples x = delayed # cascade of first-order all-pass sections for i, a in enumerate(self.a): y = -a * x + self.states[i] self.states[i] = x + a * y x = y
Leave a Reply