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, env_expand_ary, env_expand_i, 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.


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

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


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

def bits
  @bits
end

#channelsObject

1..666


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

def channels
  @channels
end

#endianObject

Returns the value of attribute endian.


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

def endian
  @endian
end

#rateObject

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


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

def rate
  @rate
end

#typeObject

s32, f32, f64 … any point in others?


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

def type
  @type
end

Class Method Details

.from_file(env, infile) ⇒ Object


57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/dtas/format.rb', line 57

def self.from_file(env, infile)
  fmt = new
  out = qx(env, %W(soxi #{infile}), err: DTAS.null)
  out =~ /^Channels\s*:\s*(\d+)/n
  fmt.channels = $1.to_i
  out =~ /^Sample Rate\s*:\s*(\d+)/n
  fmt.rate = $1.to_i
  out =~ /Precision\s*:\s*(\d+)-bit/n
  fmt.bits = $1.to_i
  fmt.type = qx(env, %W(soxi -t #{infile})).strip # ....
  fmt
end

.load(hash) ⇒ Object


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

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


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

def self.precision(env, infile)
  # sox.git f4562efd0aa3
  qx(env, %W(soxi -p #{infile}), err: DTAS.null).to_i
rescue # fallback to parsing the whole output
  s = qx(env, %W(soxi #{infile}), err: DTAS.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


106
107
108
109
110
111
112
# File 'lib/dtas/format.rb', line 106

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


115
116
117
118
119
120
# File 'lib/dtas/format.rb', line 115

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


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

def bytes_per_sample
  bits_per_sample / 8
end

#bytes_to_samples(bytes) ⇒ Object


143
144
145
# File 'lib/dtas/format.rb', line 143

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

#bytes_to_time(bytes) ⇒ Object


147
148
149
# File 'lib/dtas/format.rb', line 147

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

#endian2Object

returns 'be' or 'le' depending on endianess


83
84
85
86
87
88
89
90
91
92
# File 'lib/dtas/format.rb', line 83

def endian2
  case e = @endian || NATIVE_ENDIAN
  when "big"
    "be"
  when "little"
    "le"
  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)

161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/dtas/format.rb', line 161

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


94
95
96
# File 'lib/dtas/format.rb', line 94

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

#to_envObject


126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/dtas/format.rb', line 126

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,
  }
  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


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

def to_hash
  ivars_to_hash(SIVS)
end

#to_hshObject


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

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

#to_sox_argObject


76
77
78
79
80
# File 'lib/dtas/format.rb', line 76

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)

155
156
157
# File 'lib/dtas/format.rb', line 155

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

#valid_type?(type) ⇒ Boolean

Returns:

  • (Boolean)

151
152
153
# File 'lib/dtas/format.rb', line 151

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