Class: DTAS::PartStats
- Inherits:
-
Object
- Object
- DTAS::PartStats
- Defined in:
- lib/dtas/partstats.rb
Overview
backend for the dtas-partstats(1) command Unlike the stuff for dtas-player, dtas-partstats is fairly tied to sox
Defined Under Namespace
Classes: TrimPart
Constant Summary collapse
- CMD =
:nodoc:
'sox "$INFILE" -n $TRIMFX $SOXFX stats $STATSOPTS'
Constants included from Process
Instance Attribute Summary collapse
-
#key_idx ⇒ Object
readonly
Returns the value of attribute key_idx.
-
#key_width ⇒ Object
readonly
Returns the value of attribute key_width.
Instance Method Summary collapse
-
#initialize(infile) ⇒ PartStats
constructor
A new instance of PartStats.
-
#parse_stats(stats, trim_part, buf) ⇒ Object
Overall Left Right DC offset 0.001074 0.000938 0.001074 Min level -0.997711 -0.997711 -0.997711 Max level 0.997681 0.997681 0.997681 Pk lev dB -0.02 -0.02 -0.02 RMS lev dB -10.38 -9.90 -10.92 RMS Pk dB -4.62 -4.62 -5.10 RMS Tr dB -87.25 -86.58 -87.25 Crest factor - 3.12 3.51 Flat factor 19.41 19.66 18.89 Pk count 117k 156k 77.4k Bit-depth 16/16 16/16 16/16 Num samples 17.2M Length s 389.373 Scale max 1.000000 Window s 0.050.
- #partitions(chunk_sec) ⇒ Object
- #partstats_spawn(trim_part, opts) ⇒ Object
- #run(opts = {}) ⇒ Object
Methods included from SpawnFix
Methods included from Process
#dtas_spawn, #env_expand, #env_expand_ary, #env_expand_i, #qx, reaper
Methods included from XS
Constructor Details
#initialize(infile) ⇒ PartStats
Returns a new instance of PartStats.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/dtas/partstats.rb', line 29 def initialize(infile) @infile = infile %w(samples rate channels).each do |iv| sw = iv[0] # -s, -r, -c i = qx(%W(soxi -#{sw} #@infile)).to_i raise ArgumentError, "invalid #{iv}: #{i}" if i <= 0 instance_variable_set("@#{iv}", i) end # "Pk lev dB" => 1, "RMS lev dB" => 2, ... @key_nr = 0 @key_idx = Hash.new { |h,k| h[k] = (@key_nr += 1) } @key_width = {} end |
Instance Attribute Details
#key_idx ⇒ Object (readonly)
Returns the value of attribute key_idx.
16 17 18 |
# File 'lib/dtas/partstats.rb', line 16 def key_idx @key_idx end |
#key_width ⇒ Object (readonly)
Returns the value of attribute key_width.
17 18 19 |
# File 'lib/dtas/partstats.rb', line 17 def key_width @key_width end |
Instance Method Details
#parse_stats(stats, trim_part, buf) ⇒ Object
Overall Left Right DC offset 0.001074 0.000938 0.001074 Min level -0.997711 -0.997711 -0.997711 Max level 0.997681 0.997681 0.997681 Pk lev dB -0.02 -0.02 -0.02 RMS lev dB -10.38 -9.90 -10.92 RMS Pk dB -4.62 -4.62 -5.10 RMS Tr dB -87.25 -86.58 -87.25 Crest factor - 3.12 3.51 Flat factor 19.41 19.66 18.89 Pk count 117k 156k 77.4k Bit-depth 16/16 16/16 16/16 Num samples 17.2M Length s 389.373 Scale max 1.000000 Window s 0.050
becomes:
[
TrimPart,
[ -0.02, -0.02, -0.02 ], # Pk lev dB
[ -10.38, -9.90, -10.92 ], # RMS lev dB
...
]
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/dtas/partstats.rb', line 161 def parse_stats(stats, trim_part, buf) trim_row = [ trim_part ] buf.split("\n").each do |line| do_map = true case line when /\A(\S+ \S+ dB)\s/, /\A(Crest factor)\s+-\s/ nshift = 3 when /\A(Flat factor)\s/ nshift = 2 when /\A(Pk count)\s/ nshift = 2 do_map = false else next end key = DTAS.dedupe_str($1) key_idx = @key_idx[key] parts = line.split(/\s+/) nshift.times { parts.shift } # remove stuff we don't need @key_width[key] = parts.size trim_row[key_idx] = do_map ? parts.map!(&:to_f) : parts end stats[trim_part.tbeg / trim_part.tlen] = trim_row end |
#partitions(chunk_sec) ⇒ Object
44 45 46 47 48 49 50 51 52 53 |
# File 'lib/dtas/partstats.rb', line 44 def partitions(chunk_sec) n = 0 part_samples = chunk_sec * @rate rv = [] begin rv << TrimPart.new(n, part_samples, @rate) n += part_samples end while n < @samples rv end |
#partstats_spawn(trim_part, opts) ⇒ Object
55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/dtas/partstats.rb', line 55 def partstats_spawn(trim_part, opts) rd, wr = IO.pipe env = opts[:env] env = env ? env.dup : {} env["INFILE"] = xs(@infile) env["TRIMFX"] = "trim #{trim_part.tbeg}s #{trim_part.tlen}s" opts = { pgroup: true, close_others: true, err: wr } pid = spawn(env, CMD, opts) wr.close [ pid, rd ] end |
#run(opts = {}) ⇒ Object
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/dtas/partstats.rb', line 67 def run(opts = {}) sev = DTAS::Sigevent.new trap(:CHLD) { sev.signal } jobs = opts[:jobs] || 2 pids = {} rset = {} stats = [] fails = [] do_spawn = lambda do |trim_part| pid, rpipe = partstats_spawn(trim_part, opts) rset[rpipe] = [ trim_part, ''.b ] pids[pid] = [ trim_part, rpipe ] end parts = partitions(opts[:chunk_length] || 10) jobs.times do trim_part = parts.shift or break do_spawn.call(trim_part) end rset[sev] = true while pids.size > 0 r = IO.select(rset.keys) or next r[0].each do |rd| if DTAS::Sigevent === rd rd.readable_iter do |_,_| begin pid, status = Process.waitpid2(-1, Process::WNOHANG) pid or break done = pids.delete(pid) done_part = done[0] if status.success? trim_part = parts.shift and do_spawn.call(trim_part) puts "DONE #{done_part}" if $DEBUG else fails << [ done_part, status ] end rescue Errno::ECHILD break end while true end else # spurious wakeup should not happen on local pipes, # so readpartial should be safe trim_part, buf = rset[rd] begin buf << rd.readpartial(666) rescue EOFError rset.delete(rd) rd.close parse_stats(stats, trim_part, buf) end end end end return stats if fails.empty? && parts.empty? fails.each do |(trim_part,status)| warn "FAIL #{status.inspect} #{trim_part}" end false ensure sev.close end |