about summary refs log tree commit homepage
path: root/lib/dtas/rg_state.rb
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2013-08-24 09:54:45 +0000
committerEric Wong <normalperson@yhbt.net>2013-08-24 09:54:45 +0000
commit3e09ac0c10c95bb24a08af62393b4f761e2743d0 (patch)
tree778dffa2ba8798503fc047db0feef6d65426ea22 /lib/dtas/rg_state.rb
downloaddtas-3e09ac0c10c95bb24a08af62393b4f761e2743d0.tar.gz
Diffstat (limited to 'lib/dtas/rg_state.rb')
-rw-r--r--lib/dtas/rg_state.rb100
1 files changed, 100 insertions, 0 deletions
diff --git a/lib/dtas/rg_state.rb b/lib/dtas/rg_state.rb
new file mode 100644
index 0000000..6463be7
--- /dev/null
+++ b/lib/dtas/rg_state.rb
@@ -0,0 +1,100 @@
+# -*- encoding: binary -*-
+# :stopdoc:
+# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
+# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
+#
+# provides support for generating appropriate effects for ReplayGain
+# MAYBE: account for non-standard reference loudness (89.0 dB is standard)
+require_relative '../dtas'
+require_relative 'serialize'
+class DTAS::RGState
+  include DTAS::Serialize
+
+  RG_MODE = {
+    # attribute name => method to use
+    "album_gain" => :rg_vol_gain,
+    "track_gain" => :rg_vol_gain,
+    "album_peak" => :rg_vol_norm,
+    "track_peak" => :rg_vol_norm,
+  }
+
+  RG_DEFAULT = {
+    # skip the effect if the adjustment is too small to be noticeable
+    "gain_threshold" => 0.00000001, # in dB
+    "norm_threshold" => 0.00000001,
+
+    "preamp" => 0, # no extra adjustment
+    # "mode" => "album_gain", # nil: off
+    "mode" => nil, # nil: off
+    "fallback_gain" => -6.0, # adjustment dB if necessary RG tag is missing
+    "fallback_track" => true,
+    "norm_level" => 1.0, # dBFS
+  }
+
+  SIVS = RG_DEFAULT.keys
+  SIVS.each { |iv| attr_accessor iv }
+
+  def initialize
+    RG_DEFAULT.each do |k,v|
+      instance_variable_set("@#{k}", v)
+    end
+  end
+
+  def self.load(hash)
+    rv = new
+    hash.each { |k,v| rv.__send__("#{k}=", v) } if hash
+    rv
+  end
+
+  def to_hash
+    ivars_to_hash(SIVS)
+  end
+
+  def to_hsh
+    # no point in dumping default values, it's just a waste of space
+    to_hash.delete_if { |k,v| RG_DEFAULT[k] == v }
+  end
+
+  # returns a dB argument to the "vol" effect, nil if nothing found
+  def rg_vol_gain(val)
+    val = val.to_f + @preamp
+    return if val.abs < @gain_threshold
+    sprintf('vol %0.8gdB', val)
+  end
+
+  # returns a linear argument to the "vol" effect
+  def rg_vol_norm(val)
+    diff = @norm_level - val.to_f
+    return if (@norm_level - diff).abs < @norm_threshold
+    diff += @norm_level
+    sprintf('vol %0.8g', diff)
+  end
+
+  # The ReplayGain fallback adjustment value (in dB), in case a file is
+  # missing ReplayGain tags.  This is useful to avoid damage to speakers,
+  # eardrums and amplifiers in case a file without then necessary ReplayGain
+  # tag slips into the queue
+  def rg_fallback_effect(reason)
+    @fallback_gain or return
+    warn(reason) if $DEBUG
+    "vol #{@fallback_gain + @preamp}dB"
+  end
+
+  # returns an array (for command-line argument) for the effect needed
+  # to apply ReplayGain
+  # this may return nil
+  def effect(source)
+    return unless @mode
+    rg = source.replaygain or
+      return rg_fallback_effect("ReplayGain tags missing")
+    val = rg.__send__(@mode)
+    if ! val && @fallback_track && @mode =~ /\Aalbum_(\w+)/
+      tag = "track_#$1"
+      val = rg.__send__(tag) or
+        return rg_fallback_effect("ReplayGain tag for #@mode missing")
+      warn("tag for #@mode missing, using #{tag}")
+    end
+    # this may be nil if the adjustment is too small:
+    __send__(RG_MODE[@mode], val)
+  end
+end