Class: DTAS::Format

Inherits:
Object
  • Object
show all
Extended by:
Process
Includes:
Process, Serialize
Defined in:
lib/dtas/format.rb

Overview

class represents an audio format (type/bits/channels/sample rate/…) used throughout dtas

Constant Summary collapse

NATIVE_ENDIAN =
[1].pack("l") == [1].pack("l>") ? "big" : "little"
FORMAT_DEFAULTS =
{
  "type" => "s32",
  "channels" => 2,
  "rate" => 44100,
  "bits" => nil,   # default: implied from type
  "endian" => nil, # unspecified
}
SIVS =

unspecified

FORMAT_DEFAULTS.keys

Constants included from Process

Process::PIDS

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Process

dtas_spawn, env_expand, qx, reaper

Methods included from SpawnFix

#spawn

Methods included from XS

#xs

Methods included from Serialize

#ivars_to_hash

Constructor Details

#initializeFormat

Returns a new instance of Format.



65
66
67
68
69
# File 'lib/dtas/format.rb', line 65

def initialize
  FORMAT_DEFAULTS.each do |k,v|
    instance_variable_set("@#{k}", v)
  end
end

Instance Attribute Details

#bitsObject

only set for playback on 16-bit DACs



18
19
20
# File 'lib/dtas/format.rb', line 18

def bits
  @bits
end

#channelsObject

1..666



16
17
18
# File 'lib/dtas/format.rb', line 16

def channels
  @channels
end

#endianObject

Returns the value of attribute endian.



19
20
21
# File 'lib/dtas/format.rb', line 19

def endian
  @endian
end

#rateObject

44100, 48000, 88200, 96000, 176400, 192000 …



17
18
19
# File 'lib/dtas/format.rb', line 17

def rate
  @rate
end

#typeObject

s32, f32, f64 … any point in others?



15
16
17
# File 'lib/dtas/format.rb', line 15

def type
  @type
end

Class Method Details

.from_file(env, infile) ⇒ Object



56
57
58
59
60
61
62
63
# File 'lib/dtas/format.rb', line 56

def self.from_file(env, infile)
  fmt = new
  fmt.channels = qx(env, %W(soxi -c #{infile})).to_i
  fmt.type = qx(env, %W(soxi -t #{infile})).strip
  fmt.rate = qx(env, %W(soxi -r #{infile})).to_i
  fmt.bits ||= precision(env, infile)
  fmt
end

.load(hash) ⇒ Object



30
31
32
33
34
35
36
37
# File 'lib/dtas/format.rb', line 30

def self.load(hash)
  fmt = new
  return fmt unless hash
  (SIVS & hash.keys).each do |k|
    fmt.instance_variable_set("@#{k}", hash[k])
  end
  fmt
end

.precision(env, infile) ⇒ Object



45
46
47
48
49
50
51
52
53
54
# File 'lib/dtas/format.rb', line 45

def self.precision(env, infile)
  # sox.git f4562efd0aa3
  qx(env, %W(soxi -p #{infile}), err: "/dev/null").to_i
rescue # fallback to parsing the whole output
  s = qx(env, %W(soxi #{infile}), err: "/dev/null")
  s =~ /Precision\s+:\s*(\d+)-bit/n
  v = $1.to_i
  return v if v > 0
  raise TypeError, "could not determine precision for #{infile}"
end

Instance Method Details

#==(other) ⇒ Object



111
112
113
114
115
116
117
# File 'lib/dtas/format.rb', line 111

def ==(other)
  a = to_hash
  b = other.to_hash
  a["bits"] ||= bits_per_sample
  b["bits"] ||= other.bits_per_sample
  a == b
end

#bits_per_sampleObject

for the decoded output



120
121
122
123
124
125
# File 'lib/dtas/format.rb', line 120

def bits_per_sample
  return @bits if @bits
  /\A[fst](8|16|24|32|64)\z/ =~ @type or
    raise TypeError, "invalid type=#@type (must be s32/f32/f64)"
  $1.to_i
end

#bytes_per_sampleObject



127
128
129
# File 'lib/dtas/format.rb', line 127

def bytes_per_sample
  bits_per_sample / 8
end

#bytes_to_samples(bytes) ⇒ Object



149
150
151
# File 'lib/dtas/format.rb', line 149

def bytes_to_samples(bytes)
  bytes / bytes_per_sample / @channels
end

#bytes_to_time(bytes) ⇒ Object



153
154
155
# File 'lib/dtas/format.rb', line 153

def bytes_to_time(bytes)
  Time.at(bytes_to_samples(bytes) / @rate.to_f)
end

#endian2Object

returns ‘be’ or ‘le’ depending on endianess



78
79
80
81
82
83
84
85
86
87
# File 'lib/dtas/format.rb', line 78

def endian2
  case e = @endian || NATIVE_ENDIAN
  when "big"
    "be"
  when "little"
    "le"
  else
    raise"unsupported endian=#{e}"
  end
end

#endian_opusencObject

returns 1 or 0 depending on endianess



90
91
92
93
94
95
96
97
# File 'lib/dtas/format.rb', line 90

def endian_opusenc
  case e = @endian || NATIVE_ENDIAN
  when "big" then "1"
  when "little" then "0"
  else
    raise"unsupported endian=#{e}"
  end
end

#hhmmss_to_samples(hhmmss) ⇒ Object

HH:MM:SS.frac (don’t bother with more complex times, too much code) part of me wants to drop this feature from playq, feels like bloat…

Raises:

  • (ArgumentError)


167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/dtas/format.rb', line 167

def hhmmss_to_samples(hhmmss)
  Numeric === hhmmss and return hhmmss * @rate
  time = hhmmss.dup
  rv = 0
  if time.sub!(/\.(\d+)\z/, "")
    # convert fractional second to sample count:
    rv = ("0.#$1".to_f * @rate).to_i
  end

  # deal with HH:MM:SS
  t = time.split(/:/)
  raise ArgumentError, "Bad time format: #{hhmmss}" if t.size > 3

  mult = 1
  while part = t.pop
    rv += part.to_i * mult * @rate
    mult *= 60
  end
  rv
end

#to_eca_argObject



99
100
101
# File 'lib/dtas/format.rb', line 99

def to_eca_arg
  %W(-f #{@type}_#{endian2},#@channels,#@rate)
end

#to_envObject



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/dtas/format.rb', line 131

def to_env
  rv = {
    "SOX_FILETYPE" => @type,
    "CHANNELS" => @channels.to_s,
    "RATE" => @rate.to_s,
    "ENDIAN" => @endian || NATIVE_ENDIAN,
    "SOXFMT" => to_sox_arg.join(' '),
    "ECAFMT" => to_eca_arg.join(' '),
    "ENDIAN2" => endian2,
    "ENDIAN_OPUSENC" => endian_opusenc,
  }
  begin # don't set these if we can't get them, SOX_FILETYPE may be enough
    rv["BITS_PER_SAMPLE"] = bits_per_sample.to_s
  rescue TypeError
  end
  rv
end

#to_hashObject



107
108
109
# File 'lib/dtas/format.rb', line 107

def to_hash
  ivars_to_hash(SIVS)
end

#to_hshObject



103
104
105
# File 'lib/dtas/format.rb', line 103

def to_hsh
  to_hash.delete_if { |k,v| v == FORMAT_DEFAULTS[k] }
end

#to_sox_argObject



71
72
73
74
75
# File 'lib/dtas/format.rb', line 71

def to_sox_arg
 rv = %W(-t#@type -c#@channels -r#@rate)
 rv.concat(%W(-b#@bits)) if @bits # needed for play(1) to 16-bit DACs
 rv
end

#valid_endian?(endian) ⇒ Boolean

Returns:

  • (Boolean)


161
162
163
# File 'lib/dtas/format.rb', line 161

def valid_endian?(endian)
  !!(endian =~ %r{\A(?:big|little|swap)\z})
end

#valid_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


157
158
159
# File 'lib/dtas/format.rb', line 157

def valid_type?(type)
  !!(type =~ %r{\A[us](?:8|16|24|32)\z} || type =~ %r{\Af(?:32|64)\z})
end