Class: Wavify::DSP::LoudnessMeter

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

Overview

ITU-R BS.1770 integrated loudness meter with EBU-style gating.

Constant Summary collapse

LOUDNESS_OFFSET =
-0.691
ABSOLUTE_GATE_LUFS =
-70.0
RELATIVE_GATE_LU =
-10.0
BLOCK_SECONDS =
0.4
STEP_SECONDS =
0.1
SHORT_TERM_SECONDS =
3.0
TRUE_PEAK_OVERSAMPLING =
4
TRUE_PEAK_RADIUS =
8

Class Method Summary collapse

Class Method Details

.integrated(samples, sample_rate: nil, channels: nil, format: nil, channel_layout: nil) ⇒ Float

Measures integrated loudness. Inputs shorter than the BS.1770 400 ms block are treated as a single partial block.

Parameters:

  • samples (Array[Numeric], Core::SampleBuffer)
  • sample_rate: (Integer, nil) (defaults to: nil)
  • channels: (Integer, nil) (defaults to: nil)
  • format: (Core::Format, nil) (defaults to: nil)
  • channel_layout: (Array[Symbol], nil) (defaults to: nil)

Returns:

  • (Float)


19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/wavify/dsp/loudness_meter.rb', line 19

def integrated(samples, sample_rate: nil, channels: nil, format: nil, channel_layout: nil)
  samples, sample_rate, channels, channel_layout = measurement_input(
    samples,
    sample_rate: sample_rate,
    channels: channels,
    format: format,
    channel_layout: channel_layout
  )
  return -Float::INFINITY if samples.empty?

  weighted = k_weight(samples, sample_rate: sample_rate, channels: channels)
  energies = block_energies(
    weighted,
    sample_rate: sample_rate,
    channels: channels,
    channel_layout: channel_layout
  )
  absolute_gated = energies.select { |energy| loudness_for_energy(energy) >= ABSOLUTE_GATE_LUFS }
  return -Float::INFINITY if absolute_gated.empty?

  relative_gate = loudness_for_energy(mean(absolute_gated)) + RELATIVE_GATE_LU
  relative_gated = absolute_gated.select { |energy| loudness_for_energy(energy) >= relative_gate }
  loudness_for_energy(mean(relative_gated))
end

.momentary(samples, sample_rate: nil, channels: nil, format: nil, channel_layout: nil) ⇒ Float

Measures the most recent 400 ms loudness window without gating.

Parameters:

  • samples (Array[Numeric], Core::SampleBuffer)
  • sample_rate: (Integer, nil) (defaults to: nil)
  • channels: (Integer, nil) (defaults to: nil)
  • format: (Core::Format, nil) (defaults to: nil)
  • channel_layout: (Array[Symbol], nil) (defaults to: nil)

Returns:

  • (Float)


45
46
47
48
49
50
51
52
53
54
# File 'lib/wavify/dsp/loudness_meter.rb', line 45

def momentary(samples, sample_rate: nil, channels: nil, format: nil, channel_layout: nil)
  window_loudness(
    samples,
    seconds: BLOCK_SECONDS,
    sample_rate: sample_rate,
    channels: channels,
    format: format,
    channel_layout: channel_layout
  )
end

.short_term(samples, sample_rate: nil, channels: nil, format: nil, channel_layout: nil) ⇒ Float

Measures the most recent 3 second loudness window without gating.

Parameters:

  • samples (Array[Numeric], Core::SampleBuffer)
  • sample_rate: (Integer, nil) (defaults to: nil)
  • channels: (Integer, nil) (defaults to: nil)
  • format: (Core::Format, nil) (defaults to: nil)
  • channel_layout: (Array[Symbol], nil) (defaults to: nil)

Returns:

  • (Float)


57
58
59
60
61
62
63
64
65
66
# File 'lib/wavify/dsp/loudness_meter.rb', line 57

def short_term(samples, sample_rate: nil, channels: nil, format: nil, channel_layout: nil)
  window_loudness(
    samples,
    seconds: SHORT_TERM_SECONDS,
    sample_rate: sample_rate,
    channels: channels,
    format: format,
    channel_layout: channel_layout
  )
end

.true_peak(samples, sample_rate: nil, channels: nil, format: nil, oversampling: TRUE_PEAK_OVERSAMPLING) ⇒ Float

Estimates inter-sample true peak with windowed-sinc oversampling.

Parameters:

  • samples (Array[Numeric], Core::SampleBuffer)
  • sample_rate: (Integer, nil) (defaults to: nil)
  • channels: (Integer, nil) (defaults to: nil)
  • format: (Core::Format, nil) (defaults to: nil)
  • oversampling: (Integer) (defaults to: TRUE_PEAK_OVERSAMPLING)

Returns:

  • (Float)


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

def true_peak(samples, sample_rate: nil, channels: nil, format: nil, oversampling: TRUE_PEAK_OVERSAMPLING)
  samples, _sample_rate, channels, = measurement_input(
    samples,
    sample_rate: sample_rate,
    channels: channels,
    format: format,
    channel_layout: nil
  )
  unless [2, 4, 8].include?(oversampling)
    raise InvalidParameterError, "oversampling must be 2, 4, or 8"
  end
  return 0.0 if samples.empty?

  channel_samples = Array.new(channels) { [] }
  samples.each_with_index { |sample, index| channel_samples.fetch(index % channels) << sample.to_f }
  channel_samples.map { |values| oversampled_peak(values, oversampling) }.max || 0.0
end