Class: Wavify::DSP::Filter
- Inherits:
-
Object
- Object
- Wavify::DSP::Filter
- Defined in:
- lib/wavify/dsp/filter.rb,
sig/dsp.rbs
Overview
Stateful biquad filter with common factory constructors.
Constant Summary collapse
- DEFAULT_TAIL_SECONDS =
Minimum tail window used to drain an IIR filter.
0.05- MAX_TAIL_SECONDS =
Hard limit on automatic filter tail rendering.
10.0- TAIL_AMPLITUDE =
Amplitude below which an IIR tail is treated as silent.
1.0e-6
Instance Attribute Summary collapse
-
#cutoff ⇒ Float
readonly
Returns the value of attribute cutoff.
-
#gain_db ⇒ Float
readonly
Returns the value of attribute gain_db.
-
#q ⇒ Float
readonly
Returns the value of attribute q.
-
#type ⇒ Symbol
readonly
Returns the value of attribute type.
Class Method Summary collapse
- .bandpass(center: nil, bandwidth: nil, cutoff: nil, q: nil) ⇒ Filter
-
.highpass(cutoff:, q: 0.707) ⇒ Filter
Builds a high-pass filter.
-
.highshelf(cutoff:, gain_db:) ⇒ Filter
Builds a high-shelf EQ filter.
-
.lowpass(cutoff:, q: 0.707) ⇒ Filter
Builds a low-pass filter.
-
.lowshelf(cutoff:, gain_db:) ⇒ Filter
Builds a low-shelf EQ filter.
-
.notch(cutoff:, q: 0.707) ⇒ Filter
Builds a notch filter.
-
.peaking(cutoff:, q: 1.0, gain_db: 0.0) ⇒ Filter
Builds a peaking EQ filter.
Instance Method Summary collapse
- #apply(buffer) ⇒ Core::SampleBuffer
- #build_runtime ⇒ Filter
-
#flush(format: nil) ⇒ Enumerator<Core::SampleBuffer>?
Emits the decaying IIR state after the input ends.
-
#initialize(type, cutoff:, q: 0.707, gain_db: 0.0) ⇒ Filter
constructor
A new instance of Filter.
- #latency ⇒ Float
- #lookahead ⇒ Float
-
#process(buffer) ⇒ Core::SampleBuffer
Processes a streaming chunk while preserving filter state.
- #process_sample(sample, sample_rate:, channel: 0) ⇒ Float
-
#reset ⇒ void
Clears internal filter state for all channels.
-
#tail_duration ⇒ Float
Estimated time for the biquad poles to decay below -120 dB.
Constructor Details
#initialize(type, cutoff:, q: 0.707, gain_db: 0.0) ⇒ Filter
Returns a new instance of Filter.
72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/wavify/dsp/filter.rb', line 72 def initialize(type, cutoff:, q: 0.707, gain_db: 0.0) @type = validate_type!(type) @cutoff = validate_cutoff!(cutoff) @q = validate_q!(q) @gain_db = validate_gain!(gain_db) @coefficients = nil @coeff_sample_rate = nil @channel_states = [] @runtime_channels = nil end |
Instance Attribute Details
#cutoff ⇒ Float (readonly)
Returns the value of attribute cutoff.
15 16 17 |
# File 'lib/wavify/dsp/filter.rb', line 15 def cutoff @cutoff end |
#gain_db ⇒ Float (readonly)
Returns the value of attribute gain_db.
15 16 17 |
# File 'lib/wavify/dsp/filter.rb', line 15 def gain_db @gain_db end |
#q ⇒ Float (readonly)
Returns the value of attribute q.
15 16 17 |
# File 'lib/wavify/dsp/filter.rb', line 15 def q @q end |
#type ⇒ Symbol (readonly)
Returns the value of attribute type.
15 16 17 |
# File 'lib/wavify/dsp/filter.rb', line 15 def type @type end |
Class Method Details
.bandpass(center: nil, bandwidth: nil, cutoff: nil, q: nil) ⇒ Filter
31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/wavify/dsp/filter.rb', line 31 def self.bandpass(center: nil, bandwidth: nil, cutoff: nil, q: nil) return new(:bandpass, cutoff: cutoff, q: q || 0.707) if cutoff unless center.is_a?(Numeric) && center.respond_to?(:finite?) && center.finite? && center.positive? raise InvalidParameterError, "center must be a positive finite Numeric" end unless bandwidth.is_a?(Numeric) && bandwidth.respond_to?(:finite?) && bandwidth.finite? && bandwidth.positive? raise InvalidParameterError, "bandwidth must be a positive finite Numeric" end new(:bandpass, cutoff: center, q: center.to_f / bandwidth) end |
.highpass(cutoff:, q: 0.707) ⇒ Filter
Builds a high-pass filter.
27 28 29 |
# File 'lib/wavify/dsp/filter.rb', line 27 def self.highpass(cutoff:, q: 0.707) new(:highpass, cutoff: cutoff, q: q) end |
.highshelf(cutoff:, gain_db:) ⇒ Filter
Builds a high-shelf EQ filter.
68 69 70 |
# File 'lib/wavify/dsp/filter.rb', line 68 def self.highshelf(cutoff:, gain_db:) new(:highshelf, cutoff: cutoff, gain_db: gain_db) end |
.lowpass(cutoff:, q: 0.707) ⇒ Filter
Builds a low-pass filter.
20 21 22 |
# File 'lib/wavify/dsp/filter.rb', line 20 def self.lowpass(cutoff:, q: 0.707) new(:lowpass, cutoff: cutoff, q: q) end |
.lowshelf(cutoff:, gain_db:) ⇒ Filter
Builds a low-shelf EQ filter.
61 62 63 |
# File 'lib/wavify/dsp/filter.rb', line 61 def self.lowshelf(cutoff:, gain_db:) new(:lowshelf, cutoff: cutoff, gain_db: gain_db) end |
.notch(cutoff:, q: 0.707) ⇒ Filter
Builds a notch filter.
47 48 49 |
# File 'lib/wavify/dsp/filter.rb', line 47 def self.notch(cutoff:, q: 0.707) new(:notch, cutoff: cutoff, q: q) end |
.peaking(cutoff:, q: 1.0, gain_db: 0.0) ⇒ Filter
Builds a peaking EQ filter.
54 55 56 |
# File 'lib/wavify/dsp/filter.rb', line 54 def self.peaking(cutoff:, q: 1.0, gain_db: 0.0) new(:peaking, cutoff: cutoff, q: q, gain_db: gain_db) end |
Instance Method Details
#apply(buffer) ⇒ Core::SampleBuffer
84 85 86 |
# File 'lib/wavify/dsp/filter.rb', line 84 def apply(buffer) DSP::Processor.render(self, buffer) end |
#build_runtime ⇒ Filter
170 171 172 |
# File 'lib/wavify/dsp/filter.rb', line 170 def build_runtime dup.reset end |
#flush(format: nil) ⇒ Enumerator<Core::SampleBuffer>?
Emits the decaying IIR state after the input ends.
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/wavify/dsp/filter.rb', line 124 def flush(format: nil) return nil unless @coeff_sample_rate && filter_state_active? if format && !format.is_a?(Core::Format) raise InvalidParameterError, "format must be Core::Format" end channels = @channel_states.length runtime_format = Core::Format.new( channels: channels, sample_rate: @coeff_sample_rate, bit_depth: 32, sample_format: :float ) remaining = tail_frame_count(@coeff_sample_rate) target_format = format || runtime_format Enumerator.new do |yielder| while remaining.positive? frames = [remaining, DSP::Processor::TAIL_CHUNK_FRAMES].min silence = Array.new(frames * channels, 0.0) processed = process_interleaved(silence, sample_rate: @coeff_sample_rate, channels: channels) yielder << Core::SampleBuffer.new(processed, runtime_format).convert(target_format) remaining -= frames end ensure reset end end |
#latency ⇒ Float
174 175 176 |
# File 'lib/wavify/dsp/filter.rb', line 174 def latency 0.0 end |
#lookahead ⇒ Float
178 179 180 |
# File 'lib/wavify/dsp/filter.rb', line 178 def lookahead 0.0 end |
#process(buffer) ⇒ Core::SampleBuffer
Processes a streaming chunk while preserving filter state.
89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/wavify/dsp/filter.rb', line 89 def process(buffer) raise InvalidParameterError, "buffer must be Core::SampleBuffer" unless buffer.is_a?(Core::SampleBuffer) float_format = buffer.format.with(sample_format: :float, bit_depth: 32) float_buffer = buffer.convert(float_format) prepare_runtime_format!(sample_rate: float_format.sample_rate, channels: float_format.channels) processed = process_interleaved( float_buffer.samples, sample_rate: float_format.sample_rate, channels: float_format.channels ) Core::SampleBuffer.new(processed, float_format).convert(buffer.format) end |
#process_sample(sample, sample_rate:, channel: 0) ⇒ Float
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/wavify/dsp/filter.rb', line 104 def process_sample(sample, sample_rate:, channel: 0) unless sample.is_a?(Numeric) && sample.respond_to?(:finite?) && sample.finite? raise InvalidParameterError, "sample must be a finite Numeric" end raise InvalidParameterError, "channel must be a non-negative Integer" unless channel.is_a?(Integer) && channel >= 0 raise InvalidParameterError, "sample_rate must be a positive Integer" unless sample_rate.is_a?(Integer) && sample_rate.positive? update_coefficients!(sample_rate) ensure_sample_channel_state!(channel) state = @channel_states[channel] x = sample.to_f y = compute_biquad(state, x) update_state!(state, x, y) y end |
#reset ⇒ void
This method returns an undefined value.
Clears internal filter state for all channels.
162 163 164 165 166 167 168 |
# File 'lib/wavify/dsp/filter.rb', line 162 def reset @coefficients = nil @coeff_sample_rate = nil @channel_states = [] @runtime_channels = nil self end |
#tail_duration ⇒ Float
Estimated time for the biquad poles to decay below -120 dB.
153 154 155 156 157 |
# File 'lib/wavify/dsp/filter.rb', line 153 def tail_duration return DEFAULT_TAIL_SECONDS unless @coeff_sample_rate && @coefficients tail_frame_count(@coeff_sample_rate).to_f / @coeff_sample_rate end |