Class: Deftones::Component::Compressor

Inherits:
Deftones::Core::AudioNode show all
Defined in:
lib/deftones/component/compressor.rb

Constant Summary collapse

DETECTORS =
%i[peak rms].freeze

Instance Attribute Summary collapse

Attributes inherited from Deftones::Core::AudioNode

#context, #input

Instance Method Summary collapse

Methods inherited from Deftones::Core::AudioNode

#>>, #attach_destination, #attach_source, #block_time, #chain, #channel_count, #channel_count_mode, #channel_interpretation, #connect, #connected?, #default_input_channels, #default_output_channels, #destination_for_connection, #detach_all_destinations, #detach_destination, #detach_source, #disconnect, #dispose, #disposed?, #fan, #get, #immediate, #input_for_index, #inputs, #mix_source_blocks, #name, #normalize_connection_index, #normalize_output_block, #now, #number_of_inputs, #number_of_outputs, #output, #output_for_connection, #output_for_index, #outputs, #raise_connection_index_error!, #reaches_node?, #render, #render_block, #sample_time, #set, #to_destination, #to_frequency, #to_master, #to_midi, #to_output, #to_s, #to_seconds, #to_ticks, #uses_legacy_render_for_block?, #validate_connectable!, #validate_connection_index!

Constructor Details

#initialize(threshold: -18.0,, ratio: 4.0, attack: 0.01, release: 0.1, detector: :peak, knee: 0.0, lookahead: 0.0, rms_window: 0.01, true_peak: false, context: Deftones.context) ⇒ Compressor

Returns a new instance of Compressor.



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/deftones/component/compressor.rb', line 11

def initialize(threshold: -18.0, ratio: 4.0, attack: 0.01, release: 0.1, detector: :peak,
               knee: 0.0, lookahead: 0.0, rms_window: 0.01, true_peak: false,
               context: Deftones.context)
  super(context: context)
  @gain_db = []
  @rms_energy = []
  @lookahead_buffers = []
  @previous_detector_samples = []
  self.threshold = threshold
  self.ratio = ratio
  self.attack = attack
  self.release = release
  self.detector = detector
  self.knee = knee
  self.lookahead = lookahead
  self.rms_window = rms_window
  self.true_peak = true_peak
end

Instance Attribute Details

#attackObject

Returns the value of attribute attack.



8
9
10
# File 'lib/deftones/component/compressor.rb', line 8

def attack
  @attack
end

#detectorObject

Returns the value of attribute detector.



8
9
10
# File 'lib/deftones/component/compressor.rb', line 8

def detector
  @detector
end

#kneeObject

Returns the value of attribute knee.



8
9
10
# File 'lib/deftones/component/compressor.rb', line 8

def knee
  @knee
end

#lookaheadObject

Returns the value of attribute lookahead.



8
9
10
# File 'lib/deftones/component/compressor.rb', line 8

def lookahead
  @lookahead
end

#lookahead_samplesObject (readonly)

Returns the value of attribute lookahead_samples.



8
9
10
# File 'lib/deftones/component/compressor.rb', line 8

def lookahead_samples
  @lookahead_samples
end

#ratioObject

Returns the value of attribute ratio.



8
9
10
# File 'lib/deftones/component/compressor.rb', line 8

def ratio
  @ratio
end

#releaseObject

Returns the value of attribute release.



8
9
10
# File 'lib/deftones/component/compressor.rb', line 8

def release
  @release
end

#rms_windowObject

Returns the value of attribute rms_window.



8
9
10
# File 'lib/deftones/component/compressor.rb', line 8

def rms_window
  @rms_window
end

#thresholdObject

Returns the value of attribute threshold.



8
9
10
# File 'lib/deftones/component/compressor.rb', line 8

def threshold
  @threshold
end

#true_peakObject

Returns the value of attribute true_peak.



8
9
10
# File 'lib/deftones/component/compressor.rb', line 8

def true_peak
  @true_peak
end

Instance Method Details

#compress(sample, channel_index) ⇒ Object (private)



86
87
88
89
90
91
92
93
94
95
96
# File 'lib/deftones/component/compressor.rb', line 86

def compress(sample, channel_index)
  level = [detector_level(sample, channel_index), 1.0e-9].max
  level_db = 20.0 * Math.log10(level)
  target_gain_db = gain_reduction_db(level_db)

  current_gain_db = @gain_db[channel_index]
  smoothing = target_gain_db < current_gain_db ? @attack_smoothing : @release_smoothing
  current_gain_db += (target_gain_db - current_gain_db) * smoothing
  @gain_db[channel_index] = current_gain_db
  lookahead_sample(sample, channel_index) * (10.0**(current_gain_db / 20.0))
end

#detector_level(sample, channel_index) ⇒ Object (private)



98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/deftones/component/compressor.rb', line 98

def detector_level(sample, channel_index)
  level =
    case @detector
    when :rms then rms_level(sample, channel_index)
    else sample.abs
    end
  return level unless @true_peak

  previous_sample = @previous_detector_samples[channel_index] || sample
  @previous_detector_samples[channel_index] = sample
  [level, sample.abs, previous_sample.abs, ((previous_sample + sample) * 0.5).abs].max
end

#ensure_channel_state(channels) ⇒ Object (private)



149
150
151
152
153
154
155
156
157
158
159
# File 'lib/deftones/component/compressor.rb', line 149

def ensure_channel_state(channels)
  required = [channels.to_i, 1].max
  @gain_db.fill(0.0, @gain_db.length...required)
  @rms_energy.fill(0.0, @rms_energy.length...required)
  @previous_detector_samples.fill(0.0, @previous_detector_samples.length...required)
  required.times do |channel_index|
    next if @lookahead_buffers[channel_index]&.length == @lookahead_samples

    @lookahead_buffers[channel_index] = Array.new(@lookahead_samples, 0.0)
  end
end

#gain_reduction_db(level_db) ⇒ Object (private)



117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/deftones/component/compressor.rb', line 117

def gain_reduction_db(level_db)
  ratio = [@ratio, 1.0].max
  return 0.0 if ratio <= 1.0
  return hard_knee_gain_reduction_db(level_db, ratio) if @knee.zero?

  over_threshold = level_db - @threshold
  half_knee = @knee * 0.5
  return 0.0 if over_threshold <= -half_knee
  return hard_knee_gain_reduction_db(level_db, ratio) if over_threshold >= half_knee

  ((1.0 / ratio) - 1.0) * ((over_threshold + half_knee)**2.0) / (2.0 * @knee)
end

#hard_knee_gain_reduction_db(level_db, ratio) ⇒ Object (private)



130
131
132
133
134
135
# File 'lib/deftones/component/compressor.rb', line 130

def hard_knee_gain_reduction_db(level_db, ratio)
  return 0.0 unless level_db > @threshold

  compressed_db = @threshold + ((level_db - @threshold) / ratio)
  compressed_db - level_db
end

#lookahead_sample(sample, channel_index) ⇒ Object (private)



137
138
139
140
141
142
143
# File 'lib/deftones/component/compressor.rb', line 137

def lookahead_sample(sample, channel_index)
  return sample if @lookahead_samples.zero?

  buffer = @lookahead_buffers[channel_index]
  buffer << sample
  buffer.shift || 0.0
end

#multichannel_process?Boolean

Returns:

  • (Boolean)


71
72
73
# File 'lib/deftones/component/compressor.rb', line 71

def multichannel_process?
  true
end

#normalize_detector(value) ⇒ Object (private)

Raises:

  • (ArgumentError)


161
162
163
164
165
166
# File 'lib/deftones/component/compressor.rb', line 161

def normalize_detector(value)
  normalized = value.to_sym
  return normalized if DETECTORS.include?(normalized)

  raise ArgumentError, "Unsupported compressor detector: #{value}"
end

#process(input_block, num_frames, _start_frame, _cache) ⇒ Object



75
76
77
78
79
80
81
82
# File 'lib/deftones/component/compressor.rb', line 75

def process(input_block, num_frames, _start_frame, _cache)
  ensure_channel_state(input_block.channels)
  Core::AudioBlock.from_channel_data(
    input_block.channel_data.each_with_index.map do |channel, channel_index|
      Array.new(num_frames) { |index| compress(channel[index], channel_index) }
    end
  )
end

#rms_level(sample, channel_index) ⇒ Object (private)



111
112
113
114
115
# File 'lib/deftones/component/compressor.rb', line 111

def rms_level(sample, channel_index)
  energy = @rms_energy[channel_index]
  @rms_energy[channel_index] = ((1.0 - @rms_smoothing) * energy) + (@rms_smoothing * sample * sample)
  Math.sqrt(@rms_energy[channel_index])
end

#smoothing_for(seconds) ⇒ Object (private)



145
146
147
# File 'lib/deftones/component/compressor.rb', line 145

def smoothing_for(seconds)
  1.0 / [(seconds.to_f * context.sample_rate), 1.0].max
end