Class: RTLSDR::DSP::Filter
- Inherits:
-
Object
- Object
- RTLSDR::DSP::Filter
- Defined in:
- lib/rtlsdr/dsp/filter.rb
Overview
FIR (Finite Impulse Response) filter class
Provides methods for designing and applying FIR filters to complex or real-valued samples. Supports lowpass, highpass, and bandpass filter types using the windowed sinc design method.
Instance Attribute Summary collapse
-
#coefficients ⇒ Array<Float>
readonly
Filter coefficients.
-
#filter_type ⇒ Symbol
readonly
Filter type (:lowpass, :highpass, :bandpass).
-
#taps ⇒ Integer
readonly
Number of filter taps.
-
#window ⇒ Symbol
readonly
Window function used (:hamming, :hanning, :blackman, :kaiser).
Class Method Summary collapse
-
.bandpass(low:, high:, sample_rate:, taps: 63, window: :hamming) ⇒ Filter
Design a bandpass FIR filter.
-
.bandstop(low:, high:, sample_rate:, taps: 63, window: :hamming) ⇒ Filter
Design a bandstop (notch) FIR filter.
-
.highpass(cutoff:, sample_rate:, taps: 63, window: :hamming) ⇒ Filter
Design a highpass FIR filter.
-
.lowpass(cutoff:, sample_rate:, taps: 63, window: :hamming) ⇒ Filter
Design a lowpass FIR filter.
Instance Method Summary collapse
-
#apply(samples) ⇒ Array<Complex, Float>
Apply the filter to samples using convolution.
-
#apply_zero_phase(samples) ⇒ Array<Complex, Float>
Apply filter with zero-phase (forward-backward filtering).
-
#frequency_response(points = 512) ⇒ Array<Float>
Get the frequency response of the filter.
-
#group_delay ⇒ Float
Get the group delay of the filter.
-
#initialize(coefficients, filter_type: :custom, window: :hamming) ⇒ Filter
constructor
Create a filter from existing coefficients.
-
#to_s ⇒ String
Human-readable filter description.
Constructor Details
#initialize(coefficients, filter_type: :custom, window: :hamming) ⇒ Filter
Create a filter from existing coefficients
159 160 161 162 163 164 |
# File 'lib/rtlsdr/dsp/filter.rb', line 159 def initialize(coefficients, filter_type: :custom, window: :hamming) @coefficients = coefficients.freeze @taps = coefficients.length @filter_type = filter_type @window = window end |
Instance Attribute Details
#coefficients ⇒ Array<Float> (readonly)
Returns Filter coefficients.
21 22 23 |
# File 'lib/rtlsdr/dsp/filter.rb', line 21 def coefficients @coefficients end |
#filter_type ⇒ Symbol (readonly)
Returns Filter type (:lowpass, :highpass, :bandpass).
27 28 29 |
# File 'lib/rtlsdr/dsp/filter.rb', line 27 def filter_type @filter_type end |
#taps ⇒ Integer (readonly)
Returns Number of filter taps.
24 25 26 |
# File 'lib/rtlsdr/dsp/filter.rb', line 24 def taps @taps end |
#window ⇒ Symbol (readonly)
Returns Window function used (:hamming, :hanning, :blackman, :kaiser).
30 31 32 |
# File 'lib/rtlsdr/dsp/filter.rb', line 30 def window @window end |
Class Method Details
.bandpass(low:, high:, sample_rate:, taps: 63, window: :hamming) ⇒ Filter
Design a bandpass FIR filter
Creates a bandpass filter that passes frequencies between low and high cutoffs and attenuates frequencies outside that range.
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/rtlsdr/dsp/filter.rb', line 96 def self.bandpass(low:, high:, sample_rate:, taps: 63, window: :hamming) raise ArgumentError, "low must be less than high" if low >= high # Ensure odd number of taps taps += 1 unless taps.odd? # Design as difference of two lowpass filters norm_low = low.to_f / sample_rate norm_high = high.to_f / sample_rate low_coeffs = design_sinc_filter(norm_low, taps, window) high_coeffs = design_sinc_filter(norm_high, taps, window) # Bandpass = highpass(low) convolved with lowpass(high) # Simpler: lowpass(high) - lowpass(low) then spectral shift # Even simpler: subtract lowpass from highpass equivalent coeffs = high_coeffs.zip(low_coeffs).map { |h, l| h - l } new(coeffs, filter_type: :bandpass, window: window) end |
.bandstop(low:, high:, sample_rate:, taps: 63, window: :hamming) ⇒ Filter
Design a bandstop (notch) FIR filter
Creates a filter that attenuates frequencies between low and high cutoffs and passes frequencies outside that range.
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/rtlsdr/dsp/filter.rb', line 128 def self.bandstop(low:, high:, sample_rate:, taps: 63, window: :hamming) raise ArgumentError, "low must be less than high" if low >= high taps += 1 unless taps.odd? norm_low = low.to_f / sample_rate norm_high = high.to_f / sample_rate low_coeffs = design_sinc_filter(norm_low, taps, window) high_coeffs = design_sinc_filter(norm_high, taps, window) # Bandstop = allpass - bandpass = lowpass(low) + highpass(high) # highpass = spectral inversion of lowpass mid = taps / 2 # Create highpass from high cutoff hp_coeffs = high_coeffs.map.with_index do |c, i| i == mid ? 1.0 - c : -c end # Add lowpass(low) + highpass(high) coeffs = low_coeffs.zip(hp_coeffs).map { |l, h| l + h } new(coeffs, filter_type: :bandstop, window: window) end |
.highpass(cutoff:, sample_rate:, taps: 63, window: :hamming) ⇒ Filter
Design a highpass FIR filter
Creates a highpass filter that passes frequencies above the cutoff and attenuates frequencies below it. Implemented via spectral inversion of a lowpass filter.
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/rtlsdr/dsp/filter.rb', line 63 def self.highpass(cutoff:, sample_rate:, taps: 63, window: :hamming) # Ensure odd number of taps for highpass taps += 1 unless taps.odd? normalized_cutoff = cutoff.to_f / sample_rate coeffs = design_sinc_filter(normalized_cutoff, taps, window) # Spectral inversion: negate all coefficients, add 1 to center tap mid = taps / 2 coeffs = coeffs.map.with_index do |c, i| if i == mid 1.0 - c else -c end end new(coeffs, filter_type: :highpass, window: window) end |
.lowpass(cutoff:, sample_rate:, taps: 63, window: :hamming) ⇒ Filter
Design a lowpass FIR filter
Creates a lowpass filter that passes frequencies below the cutoff and attenuates frequencies above it.
44 45 46 47 48 |
# File 'lib/rtlsdr/dsp/filter.rb', line 44 def self.lowpass(cutoff:, sample_rate:, taps: 63, window: :hamming) normalized_cutoff = cutoff.to_f / sample_rate coeffs = design_sinc_filter(normalized_cutoff, taps, window) new(coeffs, filter_type: :lowpass, window: window) end |
Instance Method Details
#apply(samples) ⇒ Array<Complex, Float>
Apply the filter to samples using convolution
172 173 174 175 176 |
# File 'lib/rtlsdr/dsp/filter.rb', line 172 def apply(samples) return [] if samples.empty? convolve(samples, @coefficients) end |
#apply_zero_phase(samples) ⇒ Array<Complex, Float>
Apply filter with zero-phase (forward-backward filtering)
Filters the signal twice (forward then backward) to eliminate phase distortion. The effective filter order is doubled.
185 186 187 188 189 190 191 192 193 194 195 196 |
# File 'lib/rtlsdr/dsp/filter.rb', line 185 def apply_zero_phase(samples) return [] if samples.empty? # Forward filter forward = convolve(samples, @coefficients) # Reverse reversed = forward.reverse # Backward filter backward = convolve(reversed, @coefficients) # Reverse again backward.reverse end |
#frequency_response(points = 512) ⇒ Array<Float>
Get the frequency response of the filter
Computes the magnitude response at the specified number of frequency points. Requires FFTW3 to be available.
206 207 208 209 210 211 212 213 214 215 216 217 |
# File 'lib/rtlsdr/dsp/filter.rb', line 206 def frequency_response(points = 512) raise "FFTW3 required for frequency response" unless DSP.fft_available? # Zero-pad coefficients to desired length padded = @coefficients + Array.new(points - @taps, 0.0) # Convert to complex complex_padded = padded.map { |c| Complex(c, 0) } # FFT spectrum = DSP.fft(complex_padded) # Return magnitude spectrum.map(&:abs) end |
#group_delay ⇒ Float
Get the group delay of the filter
For a symmetric FIR filter, the group delay is constant and equal to (taps - 1) / 2 samples.
225 226 227 |
# File 'lib/rtlsdr/dsp/filter.rb', line 225 def group_delay (@taps - 1) / 2.0 end |
#to_s ⇒ String
Returns Human-readable filter description.
230 231 232 |
# File 'lib/rtlsdr/dsp/filter.rb', line 230 def to_s "#{@filter_type.capitalize} FIR filter (#{@taps} taps, #{@window} window)" end |