Class: Wavify::DSP::Effects::EQ

Inherits:
Object
  • Object
show all
Defined in:
lib/wavify/dsp/effects/eq.rb,
sig/effects.rbs

Overview

Ordered chain of biquad filters for simple EQ curves.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*filters) ⇒ EQ

Returns a new instance of EQ.

Parameters:

Raises:



10
11
12
13
14
15
16
17
18
# File 'lib/wavify/dsp/effects/eq.rb', line 10

def initialize(*filters)
  @filters = filters.flatten
  raise InvalidParameterError, "at least one filter is required" if @filters.empty?

  @filters.each do |filter|
    raise InvalidParameterError, "filters must respond to :apply or :process" unless filter.respond_to?(:apply) || filter.respond_to?(:process)
  end
  @runtime_format = nil
end

Class Method Details

.simple(highpass: nil, lowpass: nil, presence: nil) ⇒ EQ

Convenience constructor for common tone-shaping bands.

Parameters:

  • highpass (Numeric, nil) (defaults to: nil)
  • lowpass (Numeric, nil) (defaults to: nil)
  • presence (Hash, nil) (defaults to: nil)

    { cutoff:, q:, gain_db: }

  • low_gain: (Numeric)
  • mid_gain: (Numeric)
  • high_gain: (Numeric)

Returns:



26
27
28
29
30
31
32
# File 'lib/wavify/dsp/effects/eq.rb', line 26

def self.simple(highpass: nil, lowpass: nil, presence: nil)
  filters = []
  filters << Wavify::DSP::Filter.highpass(cutoff: highpass) if highpass
  filters << Wavify::DSP::Filter.lowpass(cutoff: lowpass) if lowpass
  filters << Wavify::DSP::Filter.peaking(**presence) if presence
  new(filters)
end

Instance Method Details

#apply(buffer) ⇒ Core::SampleBuffer

Parameters:

Returns:



50
51
52
# File 'lib/wavify/dsp/effects/eq.rb', line 50

def apply(buffer)
  DSP::Processor.render(self, buffer)
end

#build_runtimeEQ

Returns:



63
64
65
# File 'lib/wavify/dsp/effects/eq.rb', line 63

def build_runtime
  self.class.new(@filters.map { |filter| DSP::Processor.build_runtime(filter) })
end

#flush(format: nil) ⇒ Object

Drains the filters' IIR state through the complete EQ chain.

Parameters:

Returns:

  • (Object)


68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/wavify/dsp/effects/eq.rb', line 68

def flush(format: nil)
  return nil unless @runtime_format

  Enumerator.new do |yielder|
    @filters.each_with_index do |filter, index|
      DSP::Processor.flush(filter, format: format || @runtime_format).each do |tail|
        processed = @filters.drop(index + 1).reduce(tail) do |current, downstream|
          DSP::Processor.process(downstream, current)
        end
        yielder << processed
      end
    end
  ensure
    @runtime_format = nil
  end
end

#latencyFloat

Returns:

  • (Float)


86
87
88
# File 'lib/wavify/dsp/effects/eq.rb', line 86

def latency
  @filters.sum { |filter| DSP::Processor.duration(filter, :latency) }
end

#lookaheadFloat

Returns:

  • (Float)


91
92
93
# File 'lib/wavify/dsp/effects/eq.rb', line 91

def lookahead
  @filters.sum { |filter| DSP::Processor.duration(filter, :lookahead) }
end

#process(buffer) ⇒ Wavify::Core::SampleBuffer

Applies all filters in order.



38
39
40
41
42
43
44
45
46
47
48
# File 'lib/wavify/dsp/effects/eq.rb', line 38

def process(buffer)
  raise InvalidParameterError, "buffer must be Core::SampleBuffer" unless buffer.is_a?(Core::SampleBuffer)

  original_format = buffer.format
  float_format = original_format.with(sample_format: :float, bit_depth: 32)
  processed = @filters.reduce(buffer.convert(float_format)) do |current, filter|
    DSP::Processor.process(filter, current)
  end.convert(original_format)
  @runtime_format = buffer.format
  processed
end

#resetEQ

Resets stateful filters in the chain.

Returns:

  • (EQ)

    self



57
58
59
60
61
# File 'lib/wavify/dsp/effects/eq.rb', line 57

def reset
  @filters.each { |filter| filter.reset if filter.respond_to?(:reset) }
  @runtime_format = nil
  self
end

#tail_durationFloat

Returns:

  • (Float)


96
97
98
# File 'lib/wavify/dsp/effects/eq.rb', line 96

def tail_duration
  @filters.sum { |filter| DSP::Processor.duration(filter, :tail_duration) }
end