jstrait-wavefile 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +17 -1
- data/lib/wavefile.rb +110 -17
- metadata +1 -1
data/README
CHANGED
@@ -6,13 +6,21 @@ First, install the WaveFile gem...
|
|
6
6
|
|
7
7
|
...and include it in your Ruby program:
|
8
8
|
|
9
|
-
require '
|
9
|
+
require 'wavefile'
|
10
10
|
|
11
11
|
To open a wave file and get the raw sample data:
|
12
12
|
|
13
13
|
w = WaveFile.open("myfile.wav")
|
14
14
|
samples = w.sample_data
|
15
15
|
|
16
|
+
Sample data is stored in an array. For mono files, each sample is a single number. For stereo files, each sample is represented by an array containing a value for the left and right channel.
|
17
|
+
|
18
|
+
# Mono example
|
19
|
+
[0, 128, 255, 128]
|
20
|
+
|
21
|
+
# Stereo example
|
22
|
+
[[0, 255], [128, 128], [255, 0], [128, 128]]
|
23
|
+
|
16
24
|
You can also get the sample data in a normalized form, with each sample between -1.0 and 1.0:
|
17
25
|
|
18
26
|
normalized_samples = w.normalized_sample_data
|
@@ -20,6 +28,8 @@ You can also get the sample data in a normalized form, with each sample between
|
|
20
28
|
You can get basic metadata:
|
21
29
|
|
22
30
|
w.num_channels # 1 for mono, 2 for stereo
|
31
|
+
w.mono? # Alias for num_channels == 1
|
32
|
+
w.stereo? # Alias for num_channels == 2
|
23
33
|
w.sample_rate # 11025, 22050, 44100, etc.
|
24
34
|
w.bits_per_sample # 8 or 16
|
25
35
|
|
@@ -32,3 +42,9 @@ To create and save a new wave file:
|
|
32
42
|
w.save("myfile.wav")
|
33
43
|
|
34
44
|
When calling the sample_data=() method, the passed in array can contain either raw samples or normalized samples. If the first item in the array is a Float, the entire array is assumed to be normalized. Normalized samples are automatically converted into raw samples when saving.
|
45
|
+
|
46
|
+
You can reverse a file with the reverse() method:
|
47
|
+
|
48
|
+
w = WaveFile.open("myfile.wav")
|
49
|
+
w.reverse()
|
50
|
+
w.save("myfile_reversed.wav")
|
data/lib/wavefile.rb
CHANGED
@@ -55,13 +55,19 @@ class WaveFile
|
|
55
55
|
HEADER_SIZE = 36
|
56
56
|
|
57
57
|
def initialize(num_channels, sample_rate, bits_per_sample, sample_data = [])
|
58
|
-
|
58
|
+
if num_channels == :mono
|
59
|
+
@num_channels = 1
|
60
|
+
elsif num_channels == :stereo
|
61
|
+
@num_channels = 2
|
62
|
+
else
|
63
|
+
@num_channels = num_channels
|
64
|
+
end
|
59
65
|
@sample_rate = sample_rate
|
60
66
|
@bits_per_sample = bits_per_sample
|
61
67
|
@sample_data = sample_data
|
62
68
|
|
63
|
-
@byte_rate = sample_rate * num_channels * (bits_per_sample / 8)
|
64
|
-
@block_align = num_channels * (bits_per_sample / 8)
|
69
|
+
@byte_rate = sample_rate * @num_channels * (bits_per_sample / 8)
|
70
|
+
@block_align = @num_channels * (bits_per_sample / 8)
|
65
71
|
end
|
66
72
|
|
67
73
|
def self.open(path)
|
@@ -70,6 +76,7 @@ class WaveFile
|
|
70
76
|
if valid_header?(header)
|
71
77
|
sample_data = read_sample_data(path,
|
72
78
|
header[:sub_chunk1_size],
|
79
|
+
header[:num_channels],
|
73
80
|
header[:bits_per_sample],
|
74
81
|
header[:sub_chunk2_size])
|
75
82
|
|
@@ -78,7 +85,7 @@ class WaveFile
|
|
78
85
|
header[:bits_per_sample],
|
79
86
|
sample_data)
|
80
87
|
else
|
81
|
-
raise StandardError, "#{path} is not a valid wave file"
|
88
|
+
raise StandardError, "#{path} is either not a valid wave file, or is in an unsupported format"
|
82
89
|
end
|
83
90
|
|
84
91
|
return wave_file
|
@@ -105,10 +112,21 @@ class WaveFile
|
|
105
112
|
file_contents += [sample_data_size].pack("V")
|
106
113
|
|
107
114
|
# Write the sample data
|
115
|
+
if !mono?
|
116
|
+
output_sample_data = []
|
117
|
+
@sample_data.each{|sample|
|
118
|
+
sample.each{|sub_sample|
|
119
|
+
output_sample_data << sub_sample
|
120
|
+
}
|
121
|
+
}
|
122
|
+
else
|
123
|
+
output_sample_data = @sample_data
|
124
|
+
end
|
125
|
+
|
108
126
|
if @bits_per_sample == 8
|
109
|
-
file_contents +=
|
127
|
+
file_contents += output_sample_data.pack("C*")
|
110
128
|
elsif @bits_per_sample == 16
|
111
|
-
file_contents +=
|
129
|
+
file_contents += output_sample_data.pack("s*")
|
112
130
|
else
|
113
131
|
raise StandardError, "Bits per sample is #{@bits_per_samples}, only 8 or 16 are supported"
|
114
132
|
end
|
@@ -124,34 +142,97 @@ class WaveFile
|
|
124
142
|
|
125
143
|
def normalized_sample_data()
|
126
144
|
if @bits_per_sample == 8
|
127
|
-
|
145
|
+
min_value = 128.0
|
146
|
+
max_value = 127.0
|
147
|
+
midpoint = 128
|
128
148
|
elsif @bits_per_sample == 16
|
129
|
-
|
149
|
+
min_value = 32768.0
|
150
|
+
max_value = 32767.0
|
151
|
+
midpoint = 0
|
130
152
|
else
|
131
153
|
raise StandardError, "Bits per sample is #{@bits_per_samples}, only 8 or 16 are supported"
|
132
154
|
end
|
133
155
|
|
156
|
+
if mono?
|
157
|
+
normalized_sample_data = @sample_data.map {|sample|
|
158
|
+
sample -= midpoint
|
159
|
+
if sample < 0
|
160
|
+
sample.to_f / min_value
|
161
|
+
else
|
162
|
+
sample.to_f / max_value
|
163
|
+
end
|
164
|
+
}
|
165
|
+
else
|
166
|
+
normalized_sample_data = @sample_data.map {|sample|
|
167
|
+
sample.map {|sub_sample|
|
168
|
+
sub_sample -= midpoint
|
169
|
+
if sub_sample < 0
|
170
|
+
sub_sample.to_f / min_value
|
171
|
+
else
|
172
|
+
sub_sample.to_f / max_value
|
173
|
+
end
|
174
|
+
}
|
175
|
+
}
|
176
|
+
end
|
177
|
+
|
134
178
|
return normalized_sample_data
|
135
179
|
end
|
136
180
|
|
137
181
|
def sample_data=(sample_data)
|
138
|
-
if sample_data.length > 0 && sample_data[0].class == Float
|
182
|
+
if sample_data.length > 0 && ((mono? && sample_data[0].class == Float) ||
|
183
|
+
(!mono? && sample_data[0][0].class == Float))
|
139
184
|
if @bits_per_sample == 8
|
140
185
|
# Samples in 8-bit wave files are stored as a unsigned byte
|
141
|
-
# Effective values are 0 to 255
|
142
|
-
|
186
|
+
# Effective values are 0 to 255, midpoint at 128
|
187
|
+
min_value = 128.0
|
188
|
+
max_value = 127.0
|
189
|
+
midpoint = 128
|
143
190
|
elsif @bits_per_sample == 16
|
144
191
|
# Samples in 16-bit wave files are stored as a signed little-endian short
|
145
|
-
# Effective values are -32768 to 32767
|
146
|
-
|
192
|
+
# Effective values are -32768 to 32767, midpoint at 0
|
193
|
+
min_value = 32768.0
|
194
|
+
max_value = 32767.0
|
195
|
+
midpoint = 0
|
147
196
|
else
|
148
197
|
raise StandardError, "Bits per sample is #{@bits_per_samples}, only 8 or 16 are supported"
|
149
198
|
end
|
199
|
+
|
200
|
+
if mono?
|
201
|
+
@sample_data = sample_data.map {|sample|
|
202
|
+
if(sample < 0.0)
|
203
|
+
(sample * min_value).round + midpoint
|
204
|
+
else
|
205
|
+
(sample * max_value).round + midpoint
|
206
|
+
end
|
207
|
+
}
|
208
|
+
else
|
209
|
+
@sample_data = sample_data.map {|sample|
|
210
|
+
sample.map {|sub_sample|
|
211
|
+
if(sub_sample < 0.0)
|
212
|
+
(sub_sample * min_value).round + midpoint
|
213
|
+
else
|
214
|
+
(sub_sample * max_value).round + midpoint
|
215
|
+
end
|
216
|
+
}
|
217
|
+
}
|
218
|
+
end
|
150
219
|
else
|
151
220
|
@sample_data = sample_data
|
152
221
|
end
|
153
222
|
end
|
154
223
|
|
224
|
+
def mono?()
|
225
|
+
return num_channels == 1
|
226
|
+
end
|
227
|
+
|
228
|
+
def stereo?()
|
229
|
+
return num_channels == 2
|
230
|
+
end
|
231
|
+
|
232
|
+
def reverse()
|
233
|
+
sample_data.reverse!()
|
234
|
+
end
|
235
|
+
|
155
236
|
attr_reader :num_channels, :sample_rate, :bits_per_sample, :byte_rate, :block_align
|
156
237
|
|
157
238
|
private
|
@@ -192,8 +273,8 @@ private
|
|
192
273
|
def self.valid_header?(header)
|
193
274
|
valid_bits_per_sample = header[:bits_per_sample] == 8 ||
|
194
275
|
header[:bits_per_sample] == 16
|
195
|
-
|
196
|
-
valid_num_channels = header[:num_channels]
|
276
|
+
|
277
|
+
valid_num_channels = (1..65535) === header[:num_channels]
|
197
278
|
|
198
279
|
return valid_bits_per_sample &&
|
199
280
|
valid_num_channels &&
|
@@ -204,7 +285,7 @@ private
|
|
204
285
|
header[:sub_chunk2_id] == SUB_CHUNK2_ID
|
205
286
|
end
|
206
287
|
|
207
|
-
def self.read_sample_data(path, sub_chunk1_size, bits_per_sample, sample_data_size)
|
288
|
+
def self.read_sample_data(path, sub_chunk1_size, num_channels, bits_per_sample, sample_data_size)
|
208
289
|
offset = 20 + sub_chunk1_size + 8
|
209
290
|
file = File.open(path, "rb")
|
210
291
|
|
@@ -217,7 +298,19 @@ private
|
|
217
298
|
data = file.sysread(sample_data_size).unpack("s*")
|
218
299
|
else
|
219
300
|
data = []
|
220
|
-
end
|
301
|
+
end
|
302
|
+
|
303
|
+
if(num_channels > 1)
|
304
|
+
multichannel_data = []
|
305
|
+
|
306
|
+
i = 0
|
307
|
+
while i < data.length
|
308
|
+
multichannel_data << data[i..(num_channels + i)]
|
309
|
+
i += num_channels
|
310
|
+
end
|
311
|
+
|
312
|
+
data = stereo_data
|
313
|
+
end
|
221
314
|
rescue EOFError
|
222
315
|
file.close()
|
223
316
|
end
|