Class: Wavify::DSP::Effects::Limiter

Inherits:
EffectBase show all
Defined in:
lib/wavify/dsp/effects/limiter.rb,
sig/effects.rbs

Overview

Stereo-linked lookahead peak limiter with attack and release smoothing.

Constant Summary collapse

MAX_LOOKAHEAD_SECONDS =
1.0

Constants inherited from EffectBase

EffectBase::TAIL_CHUNK_FRAMES

Instance Method Summary collapse

Methods inherited from EffectBase

#build_runtime, #reset

Constructor Details

#initialize(ceiling: -1.0,, input_gain: 0.0, attack: 0.001, release: 0.05, lookahead: 0.005, oversampling: 1) ⇒ Limiter

Returns a new instance of Limiter.

Parameters:

  • ceiling: (Numeric) (defaults to: -1.0,)
  • input_gain: (Numeric) (defaults to: 0.0)
  • attack: (Numeric) (defaults to: 0.001)
  • release: (Numeric) (defaults to: 0.05)
  • lookahead: (Numeric) (defaults to: 0.005)
  • oversampling: (Integer) (defaults to: 1)


52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/wavify/dsp/effects/limiter.rb', line 52

def initialize(ceiling: -1.0, input_gain: 0.0, attack: 0.001, release: 0.05, lookahead: 0.005,
               oversampling: 1)
  super()
  @ceiling_db = validate_dbfs!(ceiling, :ceiling)
  @input_gain_db = validate_finite_numeric!(input_gain, :input_gain).to_f
  @attack = validate_time!(attack, :attack)
  @release = validate_time!(release, :release)
  @lookahead = validate_time!(lookahead, :lookahead)
  @oversampling = validate_oversampling!(oversampling)
  @ceiling = db_to_amplitude(@ceiling_db)
  @input_gain = db_to_amplitude(@input_gain_db)
  reset
end

Instance Method Details

#apply(buffer) ⇒ Object

Applies the limiter on an isolated runtime without adding latency or changing any streaming state already held by this instance.



68
69
70
# File 'lib/wavify/dsp/effects/limiter.rb', line 68

def apply(buffer)
  build_runtime.send(:apply_offline, buffer)
end

#flush(format: nil) ⇒ Object

Emits samples retained by the lookahead delay.



90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/wavify/dsp/effects/limiter.rb', line 90

def flush(format: nil)
  return nil unless runtime_prepared? && @delay_frames && @lookahead_frames.positive?

  runtime_format = Core::Format.new(
    channels: @runtime_channels,
    sample_rate: @runtime_sample_rate,
    bit_depth: 32,
    sample_format: :float
  )
  output = flush_limiter_frames.flatten(1)
  target_format = format || runtime_format
  Core::SampleBuffer.new(output, runtime_format).convert(target_format)
end

#latencyObject



104
105
106
# File 'lib/wavify/dsp/effects/limiter.rb', line 104

def latency
  @lookahead
end

#lookaheadObject



108
109
110
# File 'lib/wavify/dsp/effects/limiter.rb', line 108

def lookahead
  @lookahead
end

#process(buffer) ⇒ Object

Processes a streaming chunk while preserving a lookahead delay.



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/wavify/dsp/effects/limiter.rb', line 73

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_if_needed!(sample_rate: float_format.sample_rate, channels: float_format.channels)

  output = []
  float_buffer.samples.each_slice(@runtime_channels) do |frame|
    delayed_frame = frame.map { |sample| sample * @input_gain }
    limited_frame = enqueue_and_limit(delayed_frame)
    output.concat(limited_frame || Array.new(@runtime_channels, 0.0))
  end
  Core::SampleBuffer.new(output, float_format).convert(buffer.format)
end

#process_sample(_sample, channel:, sample_rate:) ⇒ Object

Raises:

  • (NotImplementedError)


116
117
118
# File 'lib/wavify/dsp/effects/limiter.rb', line 116

def process_sample(_sample, channel:, sample_rate:)
  raise NotImplementedError, "Limiter requires frame-aware #apply or #process"
end

#tail_durationObject



112
113
114
# File 'lib/wavify/dsp/effects/limiter.rb', line 112

def tail_duration
  @lookahead
end