jstrait-wavefile 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|