1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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
132
133
134
135
136
137
138
139
140
141
142
143
144
|
# -*- encoding: binary -*-
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
# class represents an audio format (type/bits/channels/sample rate/...)
require_relative '../dtas'
require_relative 'process'
require_relative 'serialize'
class DTAS::Format # :nodoc:
include DTAS::Process
include DTAS::Serialize
NATIVE_ENDIAN = [1].pack("l") == [1].pack("l>") ? "big" : "little"
attr_accessor :type # s32, f32, f64 ... any point in others?
attr_accessor :channels # 1..666
attr_accessor :rate # 44100, 48000, 88200, 96000, 176400, 192000 ...
attr_accessor :bits # only set for playback on 16-bit DACs
attr_accessor :endian
FORMAT_DEFAULTS = {
"type" => "s32",
"channels" => 2,
"rate" => 44100,
"bits" => nil, # default: implied from type
"endian" => nil, # unspecified
}
SIVS = FORMAT_DEFAULTS.keys
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
def initialize
FORMAT_DEFAULTS.each do |k,v|
instance_variable_set("@#{k}", v)
end
end
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
# returns 'be' or 'le' depending on endianess
def endian2
case e = @endian || NATIVE_ENDIAN
when "big"
"be"
when "little"
"le"
else
raise"unsupported endian=#{e}"
end
end
def to_eca_arg
%W(-f #{@type}_#{endian2},#@channels,#@rate)
end
def inspect
"<#{self.class}(#{xs(to_sox_arg)})>"
end
def to_hsh
to_hash.delete_if { |k,v| v == FORMAT_DEFAULTS[k] }
end
def to_hash
ivars_to_hash(SIVS)
end
# for the _decoded_ output
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
def bytes_per_sample
bits_per_sample / 8
end
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
def bytes_to_samples(bytes)
bytes / bytes_per_sample / @channels
end
def bytes_to_time(bytes)
Time.at(bytes_to_samples(bytes) / @rate.to_f)
end
def valid_type?(type)
!!(type =~ %r{\A[us](?:8|16|24|32)\z} || type =~ %r{\Af(?:32|64)\z})
end
def valid_endian?(endian)
!!(endian =~ %r{\A(?:big|little|swap)\z})
end
# 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...
def hhmmss_to_samples(hhmmss)
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
end
|