wavefile 0.3.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/LICENSE +24 -0
- data/README.markdown +79 -0
- data/lib/wavefile.rb +456 -0
- data/test/wavefile_test.rb +339 -0
- metadata +58 -0
data/LICENSE
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
== WaveFile
|
2
|
+
|
3
|
+
# Copyright (c) 2009 Joel Strait
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person
|
6
|
+
# obtaining a copy of this software and associated documentation
|
7
|
+
# files (the "Software"), to deal in the Software without
|
8
|
+
# restriction, including without limitation the rights to use,
|
9
|
+
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
# copies of the Software, and to permit persons to whom the
|
11
|
+
# Software is furnished to do so, subject to the following
|
12
|
+
# conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
19
|
+
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
21
|
+
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
22
|
+
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
23
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
24
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
A Ruby gem for reading and writing wave files (*.wav).
|
2
|
+
|
3
|
+
# Installation
|
4
|
+
|
5
|
+
First, install the WaveFile gem...
|
6
|
+
|
7
|
+
gem install wavefile
|
8
|
+
|
9
|
+
...and include it in your Ruby program:
|
10
|
+
|
11
|
+
require 'wavefile'
|
12
|
+
|
13
|
+
# Usage
|
14
|
+
|
15
|
+
To open a wave file and get the raw sample data:
|
16
|
+
|
17
|
+
w = WaveFile.open("myfile.wav")
|
18
|
+
samples = w.sample_data
|
19
|
+
|
20
|
+
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.
|
21
|
+
|
22
|
+
# Mono example
|
23
|
+
[0, 128, 255, 128]
|
24
|
+
|
25
|
+
# Stereo example
|
26
|
+
[[0, 255], [128, 128], [255, 0], [128, 128]]
|
27
|
+
|
28
|
+
You can also get the sample data in a normalized form, with each sample between -1.0 and 1.0:
|
29
|
+
|
30
|
+
normalized_samples = w.normalized_sample_data
|
31
|
+
|
32
|
+
You can get basic metadata:
|
33
|
+
|
34
|
+
w.num_channels # 1 for mono, 2 for stereo
|
35
|
+
w.mono? # Alias for num_channels == 1
|
36
|
+
w.stereo? # Alias for num_channels == 2
|
37
|
+
w.sample_rate # 11025, 22050, 44100, etc.
|
38
|
+
w.bits_per_sample # 8 or 16
|
39
|
+
w.duration # Example: {:hours => 0, :minutes => 3, :seconds => 12, :milliseconds => 345 }
|
40
|
+
|
41
|
+
You can view all of the metadata at once using the `inspect()` method. It returns a multi-line string:
|
42
|
+
|
43
|
+
w.inspect()
|
44
|
+
|
45
|
+
# Example result:
|
46
|
+
# Channels: 2
|
47
|
+
# Sample rate: 44100
|
48
|
+
# Bits per sample: 16
|
49
|
+
# Block align: 4
|
50
|
+
# Byte rate: 176400
|
51
|
+
# Sample count: 498070
|
52
|
+
# Duration: 0h:0m:11s:294ms
|
53
|
+
|
54
|
+
You can use setter methods to convert a file to a different format. For example, you can convert a mono file to stereo, or down-sample a 16-bit file to 8-bit.
|
55
|
+
|
56
|
+
w.num_channels = 2
|
57
|
+
w.num_channels = :stereo // Equivalent to line above
|
58
|
+
w.sample_rate = 22050
|
59
|
+
w.bits_per_sample = 16
|
60
|
+
|
61
|
+
Changes are not saved to disk until you call the `save()` method.
|
62
|
+
|
63
|
+
w.save("myfile.wav")
|
64
|
+
|
65
|
+
To create and save a new wave file:
|
66
|
+
|
67
|
+
w = WaveFile.new(1, 44100, 16) # num_channels,
|
68
|
+
# sample_rate,
|
69
|
+
# bits_per_sample
|
70
|
+
w.sample_data = <array of samples goes here>
|
71
|
+
w.save("myfile.wav")
|
72
|
+
|
73
|
+
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.
|
74
|
+
|
75
|
+
You can reverse a file with the `reverse()` method:
|
76
|
+
|
77
|
+
w = WaveFile.open("myfile.wav")
|
78
|
+
w.reverse()
|
79
|
+
w.save("myfile_reversed.wav")
|
data/lib/wavefile.rb
ADDED
@@ -0,0 +1,456 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
=begin
|
4
|
+
WAV File Specification
|
5
|
+
FROM http://ccrma.stanford.edu/courses/422/projects/WaveFormat/
|
6
|
+
The canonical WAVE format starts with the RIFF header:
|
7
|
+
0 4 ChunkID Contains the letters "RIFF" in ASCII form
|
8
|
+
(0x52494646 big-endian form).
|
9
|
+
4 4 ChunkSize 36 + SubChunk2Size, or more precisely:
|
10
|
+
4 + (8 + SubChunk1Size) + (8 + SubChunk2Size)
|
11
|
+
This is the size of the rest of the chunk
|
12
|
+
following this number. This is the size of the
|
13
|
+
entire file in bytes minus 8 bytes for the
|
14
|
+
two fields not included in this count:
|
15
|
+
ChunkID and ChunkSize.
|
16
|
+
8 4 Format Contains the letters "WAVE"
|
17
|
+
(0x57415645 big-endian form).
|
18
|
+
|
19
|
+
The "WAVE" format consists of two subchunks: "fmt " and "data":
|
20
|
+
The "fmt " subchunk describes the sound data's format:
|
21
|
+
12 4 Subchunk1ID Contains the letters "fmt "
|
22
|
+
(0x666d7420 big-endian form).
|
23
|
+
16 4 Subchunk1Size 16 for PCM. This is the size of the
|
24
|
+
rest of the Subchunk which follows this number.
|
25
|
+
20 2 AudioFormat PCM = 1 (i.e. Linear quantization)
|
26
|
+
Values other than 1 indicate some
|
27
|
+
form of compression.
|
28
|
+
22 2 NumChannels Mono = 1, Stereo = 2, etc.
|
29
|
+
24 4 SampleRate 8000, 44100, etc.
|
30
|
+
28 4 ByteRate == SampleRate * NumChannels * BitsPerSample/8
|
31
|
+
32 2 BlockAlign == NumChannels * BitsPerSample/8
|
32
|
+
The number of bytes for one sample including
|
33
|
+
all channels. I wonder what happens when
|
34
|
+
this number isn't an integer?
|
35
|
+
34 2 BitsPerSample 8 bits = 8, 16 bits = 16, etc.
|
36
|
+
|
37
|
+
The "data" subchunk contains the size of the data and the actual sound:
|
38
|
+
36 4 Subchunk2ID Contains the letters "data"
|
39
|
+
(0x64617461 big-endian form).
|
40
|
+
40 4 Subchunk2Size == NumSamples * NumChannels * BitsPerSample/8
|
41
|
+
This is the number of bytes in the data.
|
42
|
+
You can also think of this as the size
|
43
|
+
of the read of the subchunk following this
|
44
|
+
number.
|
45
|
+
44 * Data The actual sound data.
|
46
|
+
=end
|
47
|
+
|
48
|
+
class WaveFile
|
49
|
+
CHUNK_ID = "RIFF"
|
50
|
+
FORMAT = "WAVE"
|
51
|
+
FORMAT_CHUNK_ID = "fmt "
|
52
|
+
SUB_CHUNK1_SIZE = 16
|
53
|
+
PCM = 1
|
54
|
+
DATA_CHUNK_ID = "data"
|
55
|
+
HEADER_SIZE = 36
|
56
|
+
|
57
|
+
def initialize(num_channels, sample_rate, bits_per_sample, sample_data = [])
|
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
|
65
|
+
@sample_rate = sample_rate
|
66
|
+
@bits_per_sample = bits_per_sample
|
67
|
+
@sample_data = sample_data
|
68
|
+
|
69
|
+
@byte_rate = sample_rate * @num_channels * (bits_per_sample / 8)
|
70
|
+
@block_align = @num_channels * (bits_per_sample / 8)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.open(path)
|
74
|
+
file = File.open(path, "rb")
|
75
|
+
|
76
|
+
begin
|
77
|
+
header = read_header(file)
|
78
|
+
errors = validate_header(header)
|
79
|
+
|
80
|
+
if errors == []
|
81
|
+
sample_data = read_sample_data(file,
|
82
|
+
header[:num_channels],
|
83
|
+
header[:bits_per_sample],
|
84
|
+
header[:sub_chunk2_size])
|
85
|
+
|
86
|
+
wave_file = self.new(header[:num_channels],
|
87
|
+
header[:sample_rate],
|
88
|
+
header[:bits_per_sample],
|
89
|
+
sample_data)
|
90
|
+
else
|
91
|
+
error_msg = "#{path} can't be opened, due to the following errors:\n"
|
92
|
+
errors.each {|error| error_msg += " * #{error}\n" }
|
93
|
+
raise StandardError, error_msg
|
94
|
+
end
|
95
|
+
rescue EOFError
|
96
|
+
raise StandardError, "An error occured while reading #{path}."
|
97
|
+
ensure
|
98
|
+
file.close()
|
99
|
+
end
|
100
|
+
|
101
|
+
return wave_file
|
102
|
+
end
|
103
|
+
|
104
|
+
def save(path)
|
105
|
+
# All numeric values should be saved in little-endian format
|
106
|
+
|
107
|
+
sample_data_size = @sample_data.length * @num_channels * (@bits_per_sample / 8)
|
108
|
+
|
109
|
+
# Write the header
|
110
|
+
file_contents = CHUNK_ID
|
111
|
+
file_contents += [HEADER_SIZE + sample_data_size].pack("V")
|
112
|
+
file_contents += FORMAT
|
113
|
+
file_contents += FORMAT_CHUNK_ID
|
114
|
+
file_contents += [SUB_CHUNK1_SIZE].pack("V")
|
115
|
+
file_contents += [PCM].pack("v")
|
116
|
+
file_contents += [@num_channels].pack("v")
|
117
|
+
file_contents += [@sample_rate].pack("V")
|
118
|
+
file_contents += [@byte_rate].pack("V")
|
119
|
+
file_contents += [@block_align].pack("v")
|
120
|
+
file_contents += [@bits_per_sample].pack("v")
|
121
|
+
file_contents += DATA_CHUNK_ID
|
122
|
+
file_contents += [sample_data_size].pack("V")
|
123
|
+
|
124
|
+
# Write the sample data
|
125
|
+
if !mono?
|
126
|
+
output_sample_data = []
|
127
|
+
@sample_data.each{|sample|
|
128
|
+
sample.each{|sub_sample|
|
129
|
+
output_sample_data << sub_sample
|
130
|
+
}
|
131
|
+
}
|
132
|
+
else
|
133
|
+
output_sample_data = @sample_data
|
134
|
+
end
|
135
|
+
|
136
|
+
if @bits_per_sample == 8
|
137
|
+
file_contents += output_sample_data.pack("C*")
|
138
|
+
elsif @bits_per_sample == 16
|
139
|
+
file_contents += output_sample_data.pack("s*")
|
140
|
+
else
|
141
|
+
raise StandardError, "Bits per sample is #{@bits_per_samples}, only 8 or 16 are supported"
|
142
|
+
end
|
143
|
+
|
144
|
+
file = File.open(path, "w")
|
145
|
+
file.syswrite(file_contents)
|
146
|
+
file.close
|
147
|
+
end
|
148
|
+
|
149
|
+
def sample_data()
|
150
|
+
return @sample_data
|
151
|
+
end
|
152
|
+
|
153
|
+
def normalized_sample_data()
|
154
|
+
if @bits_per_sample == 8
|
155
|
+
min_value = 128.0
|
156
|
+
max_value = 127.0
|
157
|
+
midpoint = 128
|
158
|
+
elsif @bits_per_sample == 16
|
159
|
+
min_value = 32768.0
|
160
|
+
max_value = 32767.0
|
161
|
+
midpoint = 0
|
162
|
+
else
|
163
|
+
raise StandardError, "Bits per sample is #{@bits_per_samples}, only 8 or 16 are supported"
|
164
|
+
end
|
165
|
+
|
166
|
+
if mono?
|
167
|
+
normalized_sample_data = @sample_data.map {|sample|
|
168
|
+
sample -= midpoint
|
169
|
+
if sample < 0
|
170
|
+
sample.to_f / min_value
|
171
|
+
else
|
172
|
+
sample.to_f / max_value
|
173
|
+
end
|
174
|
+
}
|
175
|
+
else
|
176
|
+
normalized_sample_data = @sample_data.map {|sample|
|
177
|
+
sample.map {|sub_sample|
|
178
|
+
sub_sample -= midpoint
|
179
|
+
if sub_sample < 0
|
180
|
+
sub_sample.to_f / min_value
|
181
|
+
else
|
182
|
+
sub_sample.to_f / max_value
|
183
|
+
end
|
184
|
+
}
|
185
|
+
}
|
186
|
+
end
|
187
|
+
|
188
|
+
return normalized_sample_data
|
189
|
+
end
|
190
|
+
|
191
|
+
def sample_data=(sample_data)
|
192
|
+
if sample_data.length > 0 && ((mono? && sample_data[0].class == Float) ||
|
193
|
+
(!mono? && sample_data[0][0].class == Float))
|
194
|
+
if @bits_per_sample == 8
|
195
|
+
# Samples in 8-bit wave files are stored as a unsigned byte
|
196
|
+
# Effective values are 0 to 255, midpoint at 128
|
197
|
+
min_value = 128.0
|
198
|
+
max_value = 127.0
|
199
|
+
midpoint = 128
|
200
|
+
elsif @bits_per_sample == 16
|
201
|
+
# Samples in 16-bit wave files are stored as a signed little-endian short
|
202
|
+
# Effective values are -32768 to 32767, midpoint at 0
|
203
|
+
min_value = 32768.0
|
204
|
+
max_value = 32767.0
|
205
|
+
midpoint = 0
|
206
|
+
else
|
207
|
+
raise StandardError, "Bits per sample is #{@bits_per_samples}, only 8 or 16 are supported"
|
208
|
+
end
|
209
|
+
|
210
|
+
if mono?
|
211
|
+
@sample_data = sample_data.map {|sample|
|
212
|
+
if(sample < 0.0)
|
213
|
+
(sample * min_value).round + midpoint
|
214
|
+
else
|
215
|
+
(sample * max_value).round + midpoint
|
216
|
+
end
|
217
|
+
}
|
218
|
+
else
|
219
|
+
@sample_data = sample_data.map {|sample|
|
220
|
+
sample.map {|sub_sample|
|
221
|
+
if(sub_sample < 0.0)
|
222
|
+
(sub_sample * min_value).round + midpoint
|
223
|
+
else
|
224
|
+
(sub_sample * max_value).round + midpoint
|
225
|
+
end
|
226
|
+
}
|
227
|
+
}
|
228
|
+
end
|
229
|
+
else
|
230
|
+
@sample_data = sample_data
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def mono?()
|
235
|
+
return num_channels == 1
|
236
|
+
end
|
237
|
+
|
238
|
+
def stereo?()
|
239
|
+
return num_channels == 2
|
240
|
+
end
|
241
|
+
|
242
|
+
def reverse()
|
243
|
+
sample_data.reverse!()
|
244
|
+
end
|
245
|
+
|
246
|
+
def duration()
|
247
|
+
total_samples = sample_data.length
|
248
|
+
samples_per_millisecond = @sample_rate / 1000.0
|
249
|
+
samples_per_second = @sample_rate
|
250
|
+
samples_per_minute = samples_per_second * 60
|
251
|
+
samples_per_hour = samples_per_minute * 60
|
252
|
+
hours, minutes, seconds, milliseconds = 0, 0, 0, 0
|
253
|
+
|
254
|
+
if(total_samples >= samples_per_hour)
|
255
|
+
hours = total_samples / samples_per_hour
|
256
|
+
total_samples -= samples_per_hour * hours
|
257
|
+
end
|
258
|
+
|
259
|
+
if(total_samples >= samples_per_minute)
|
260
|
+
minutes = total_samples / samples_per_minute
|
261
|
+
total_samples -= samples_per_minute * minutes
|
262
|
+
end
|
263
|
+
|
264
|
+
if(total_samples >= samples_per_second)
|
265
|
+
seconds = total_samples / samples_per_second
|
266
|
+
total_samples -= samples_per_second * seconds
|
267
|
+
end
|
268
|
+
|
269
|
+
milliseconds = (total_samples / samples_per_millisecond).floor
|
270
|
+
|
271
|
+
return { :hours => hours, :minutes => minutes, :seconds => seconds, :milliseconds => milliseconds }
|
272
|
+
end
|
273
|
+
|
274
|
+
def bits_per_sample=(new_bits_per_sample)
|
275
|
+
if new_bits_per_sample != 8 && new_bits_per_sample != 16
|
276
|
+
raise StandardError, "Bits per sample of #{@bits_per_samples} is invalid, only 8 or 16 are supported"
|
277
|
+
end
|
278
|
+
|
279
|
+
if @bits_per_sample == 16 && new_bits_per_sample == 8
|
280
|
+
conversion_func = lambda {|sample|
|
281
|
+
if(sample < 0)
|
282
|
+
(sample / 256) + 128
|
283
|
+
else
|
284
|
+
# Faster to just divide by integer 258?
|
285
|
+
(sample / 258.007874015748031).round + 128
|
286
|
+
end
|
287
|
+
}
|
288
|
+
|
289
|
+
if mono?
|
290
|
+
@sample_data.map! &conversion_func
|
291
|
+
else
|
292
|
+
sample_data.map! {|sample| sample.map! &conversion_func }
|
293
|
+
end
|
294
|
+
elsif @bits_per_sample == 8 && new_bits_per_sample == 16
|
295
|
+
conversion_func = lambda {|sample|
|
296
|
+
sample -= 128
|
297
|
+
if(sample < 0)
|
298
|
+
sample * 256
|
299
|
+
else
|
300
|
+
# Faster to just multiply by integer 258?
|
301
|
+
(sample * 258.007874015748031).round
|
302
|
+
end
|
303
|
+
}
|
304
|
+
|
305
|
+
if mono?
|
306
|
+
@sample_data.map! &conversion_func
|
307
|
+
else
|
308
|
+
sample_data.map! {|sample| sample.map! &conversion_func }
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
@bits_per_sample = new_bits_per_sample
|
313
|
+
end
|
314
|
+
|
315
|
+
def num_channels=(new_num_channels)
|
316
|
+
if new_num_channels == :mono
|
317
|
+
new_num_channels = 1
|
318
|
+
elsif new_num_channels == :stereo
|
319
|
+
new_num_channels = 2
|
320
|
+
end
|
321
|
+
|
322
|
+
# The cases of mono -> stereo and vice-versa are handled in specially,
|
323
|
+
# because those conversion methods are faster than the general methods,
|
324
|
+
# and the large majority of wave files are expected to be either mono or stereo.
|
325
|
+
if @num_channels == 1 && new_num_channels == 2
|
326
|
+
sample_data.map! {|sample| [sample, sample]}
|
327
|
+
elsif @num_channels == 2 && new_num_channels == 1
|
328
|
+
sample_data.map! {|sample| (sample[0] + sample[1]) / 2}
|
329
|
+
elsif @num_channels == 1 && new_num_channels >= 2
|
330
|
+
sample_data.map! {|sample| [].fill(sample, 0, new_num_channels)}
|
331
|
+
elsif @num_channels >= 2 && new_num_channels == 1
|
332
|
+
sample_data.map! {|sample| sample.inject(0) {|sub_sample, sum| sum + sub_sample } / @num_channels }
|
333
|
+
elsif @num_channels > 2 && new_num_channels == 2
|
334
|
+
sample_data.map! {|sample| [sample[0], sample[1]]}
|
335
|
+
end
|
336
|
+
|
337
|
+
@num_channels = new_num_channels
|
338
|
+
end
|
339
|
+
|
340
|
+
def inspect()
|
341
|
+
duration = self.duration()
|
342
|
+
|
343
|
+
result = "Channels: #{@num_channels}\n" +
|
344
|
+
"Sample rate: #{@sample_rate}\n" +
|
345
|
+
"Bits per sample: #{@bits_per_sample}\n" +
|
346
|
+
"Block align: #{@block_align}\n" +
|
347
|
+
"Byte rate: #{@byte_rate}\n" +
|
348
|
+
"Sample count: #{@sample_data.length}\n" +
|
349
|
+
"Duration: #{duration[:hours]}h:#{duration[:minutes]}m:#{duration[:seconds]}s:#{duration[:milliseconds]}ms\n"
|
350
|
+
end
|
351
|
+
|
352
|
+
attr_reader :num_channels, :bits_per_sample, :byte_rate, :block_align
|
353
|
+
attr_accessor :sample_rate
|
354
|
+
|
355
|
+
private
|
356
|
+
|
357
|
+
def self.read_header(file)
|
358
|
+
header = {}
|
359
|
+
|
360
|
+
# Read RIFF header
|
361
|
+
riff_header = file.sysread(12).unpack("a4Va4")
|
362
|
+
header[:chunk_id] = riff_header[0]
|
363
|
+
header[:chunk_size] = riff_header[1]
|
364
|
+
header[:format] = riff_header[2]
|
365
|
+
|
366
|
+
# Read format subchunk
|
367
|
+
header[:sub_chunk1_id], header[:sub_chunk1_size] = self.read_to_chunk(file, FORMAT_CHUNK_ID)
|
368
|
+
format_subchunk_str = file.sysread(header[:sub_chunk1_size])
|
369
|
+
format_subchunk = format_subchunk_str.unpack("vvVVvv") # Any extra parameters are ignored
|
370
|
+
header[:audio_format] = format_subchunk[0]
|
371
|
+
header[:num_channels] = format_subchunk[1]
|
372
|
+
header[:sample_rate] = format_subchunk[2]
|
373
|
+
header[:byte_rate] = format_subchunk[3]
|
374
|
+
header[:block_align] = format_subchunk[4]
|
375
|
+
header[:bits_per_sample] = format_subchunk[5]
|
376
|
+
|
377
|
+
# Read data subchunk
|
378
|
+
header[:sub_chunk2_id], header[:sub_chunk2_size] = self.read_to_chunk(file, DATA_CHUNK_ID)
|
379
|
+
|
380
|
+
return header
|
381
|
+
end
|
382
|
+
|
383
|
+
def self.read_to_chunk(file, expected_chunk_id)
|
384
|
+
chunk_id = file.sysread(4)
|
385
|
+
chunk_size = file.sysread(4).unpack("V")[0]
|
386
|
+
|
387
|
+
while chunk_id != expected_chunk_id
|
388
|
+
# Skip chunk
|
389
|
+
file.sysread(chunk_size)
|
390
|
+
|
391
|
+
chunk_id = file.sysread(4)
|
392
|
+
chunk_size = file.sysread(4).unpack("V")[0]
|
393
|
+
end
|
394
|
+
|
395
|
+
return chunk_id, chunk_size
|
396
|
+
end
|
397
|
+
|
398
|
+
def self.validate_header(header)
|
399
|
+
errors = []
|
400
|
+
|
401
|
+
unless header[:bits_per_sample] == 8 || header[:bits_per_sample] == 16
|
402
|
+
errors << "Invalid bits per sample of #{header[:bits_per_sample]}. Only 8 and 16 are supported."
|
403
|
+
end
|
404
|
+
|
405
|
+
unless (1..65535) === header[:num_channels]
|
406
|
+
errors << "Invalid number of channels. Must be between 1 and 65535."
|
407
|
+
end
|
408
|
+
|
409
|
+
unless header[:chunk_id] == CHUNK_ID
|
410
|
+
errors << "Unsupported chunk ID: '#{header[:chunk_id]}'"
|
411
|
+
end
|
412
|
+
|
413
|
+
unless header[:format] == FORMAT
|
414
|
+
errors << "Unsupported format: '#{header[:format]}'"
|
415
|
+
end
|
416
|
+
|
417
|
+
unless header[:sub_chunk1_id] == FORMAT_CHUNK_ID
|
418
|
+
errors << "Unsupported chunk id: '#{header[:sub_chunk1_id]}'"
|
419
|
+
end
|
420
|
+
|
421
|
+
unless header[:audio_format] == PCM
|
422
|
+
errors << "Unsupported audio format code: '#{header[:audio_format]}'"
|
423
|
+
end
|
424
|
+
|
425
|
+
unless header[:sub_chunk2_id] == DATA_CHUNK_ID
|
426
|
+
errors << "Unsupported chunk id: '#{header[:sub_chunk2_id]}'"
|
427
|
+
end
|
428
|
+
|
429
|
+
return errors
|
430
|
+
end
|
431
|
+
|
432
|
+
# Assumes that file is "queued up" to the first sample
|
433
|
+
def self.read_sample_data(file, num_channels, bits_per_sample, sample_data_size)
|
434
|
+
if(bits_per_sample == 8)
|
435
|
+
data = file.sysread(sample_data_size).unpack("C*")
|
436
|
+
elsif(bits_per_sample == 16)
|
437
|
+
data = file.sysread(sample_data_size).unpack("s*")
|
438
|
+
else
|
439
|
+
data = []
|
440
|
+
end
|
441
|
+
|
442
|
+
if(num_channels > 1)
|
443
|
+
multichannel_data = []
|
444
|
+
|
445
|
+
i = 0
|
446
|
+
while i < data.length
|
447
|
+
multichannel_data << data[i...(num_channels + i)]
|
448
|
+
i += num_channels
|
449
|
+
end
|
450
|
+
|
451
|
+
data = multichannel_data
|
452
|
+
end
|
453
|
+
|
454
|
+
return data
|
455
|
+
end
|
456
|
+
end
|
@@ -0,0 +1,339 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__),'..','lib')
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'wavefile'
|
5
|
+
|
6
|
+
class WaveFileTest < Test::Unit::TestCase
|
7
|
+
def test_initialize
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_read_empty_file
|
12
|
+
assert_raise(StandardError) { w = WaveFile.open("examples/invalid/empty.wav") }
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_read_nonexistent_file
|
16
|
+
assert_raise(Errno::ENOENT) { w = WaveFile.open("examples/invalid/nonexistent.wav") }
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_read_valid_file
|
20
|
+
# Mono file
|
21
|
+
w = WaveFile.open("examples/valid/sine-mono-8bit.wav")
|
22
|
+
assert_equal(w.num_channels, 1)
|
23
|
+
assert_equal(w.mono?, true)
|
24
|
+
assert_equal(w.stereo?, false)
|
25
|
+
assert_equal(w.sample_rate, 44100)
|
26
|
+
assert_equal(w.bits_per_sample, 8)
|
27
|
+
assert_equal(w.byte_rate, 44100)
|
28
|
+
assert_equal(w.block_align, 1)
|
29
|
+
assert_equal(w.sample_data.length, 44100)
|
30
|
+
# Test that sample array is in format [sample, sample ... sample]
|
31
|
+
valid = true
|
32
|
+
w.sample_data.each{|sample| valid &&= (sample.class == Fixnum)}
|
33
|
+
assert_equal(valid, true)
|
34
|
+
|
35
|
+
# Stereo file
|
36
|
+
w = WaveFile.open("examples/valid/sine-stereo-8bit.wav")
|
37
|
+
assert_equal(w.num_channels, 2)
|
38
|
+
assert_equal(w.mono?, false)
|
39
|
+
assert_equal(w.stereo?, true)
|
40
|
+
assert_equal(w.sample_rate, 44100)
|
41
|
+
assert_equal(w.bits_per_sample, 8)
|
42
|
+
assert_equal(w.byte_rate, 88200)
|
43
|
+
assert_equal(w.block_align, 2)
|
44
|
+
assert_equal(w.sample_data.length, 44100)
|
45
|
+
# Test that sample array is in format [[left, right], [left, right] ... [left,right]]
|
46
|
+
valid = true
|
47
|
+
w.sample_data.each{|sample| valid &&= (sample.class == Array) && (sample.length == 2)}
|
48
|
+
assert_equal(valid, true)
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_new_file
|
52
|
+
# Mono
|
53
|
+
w = WaveFile.new(1, 44100, 8)
|
54
|
+
assert_equal(w.num_channels, 1)
|
55
|
+
assert_equal(w.sample_rate, 44100)
|
56
|
+
assert_equal(w.bits_per_sample, 8)
|
57
|
+
assert_equal(w.byte_rate, 44100)
|
58
|
+
assert_equal(w.block_align, 1)
|
59
|
+
|
60
|
+
# Mono
|
61
|
+
w = WaveFile.new(:mono, 44100, 8)
|
62
|
+
assert_equal(w.num_channels, 1)
|
63
|
+
assert_equal(w.sample_rate, 44100)
|
64
|
+
assert_equal(w.bits_per_sample, 8)
|
65
|
+
assert_equal(w.byte_rate, 44100)
|
66
|
+
assert_equal(w.block_align, 1)
|
67
|
+
|
68
|
+
# Stereo
|
69
|
+
w = WaveFile.new(2, 44100, 16)
|
70
|
+
assert_equal(w.num_channels, 2)
|
71
|
+
assert_equal(w.sample_rate, 44100)
|
72
|
+
assert_equal(w.bits_per_sample, 16)
|
73
|
+
assert_equal(w.byte_rate, 176400)
|
74
|
+
assert_equal(w.block_align, 4)
|
75
|
+
|
76
|
+
# Stereo
|
77
|
+
w = WaveFile.new(:stereo, 44100, 16)
|
78
|
+
assert_equal(w.num_channels, 2)
|
79
|
+
assert_equal(w.sample_rate, 44100)
|
80
|
+
assert_equal(w.bits_per_sample, 16)
|
81
|
+
assert_equal(w.byte_rate, 176400)
|
82
|
+
assert_equal(w.block_align, 4)
|
83
|
+
|
84
|
+
# Quad
|
85
|
+
w = WaveFile.new(4, 44100, 16)
|
86
|
+
assert_equal(w.num_channels, 4)
|
87
|
+
assert_equal(w.sample_rate, 44100)
|
88
|
+
assert_equal(w.bits_per_sample, 16)
|
89
|
+
assert_equal(w.byte_rate, 352800)
|
90
|
+
assert_equal(w.block_align, 8)
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_normalized_sample_data
|
94
|
+
# Mono 8-bit
|
95
|
+
w = WaveFile.new(:mono, 44100, 8)
|
96
|
+
w.sample_data = [0, 32, 64, 96, 128, 160, 192, 223, 255]
|
97
|
+
assert_equal(w.normalized_sample_data, [-1.0, -0.75, -0.5, -0.25, 0.0,
|
98
|
+
(32.0 / 127.0), (64.0 / 127.0), (95.0 / 127.0), 1.0])
|
99
|
+
|
100
|
+
# Mono 16-bit
|
101
|
+
w = WaveFile.new(:mono, 44100, 16)
|
102
|
+
w.sample_data = [-32768, -24576, -16384, -8192, 0, 8192, 16383, 24575, 32767]
|
103
|
+
assert_equal(w.normalized_sample_data, [-1.0, -0.75, -0.5, -0.25, 0.0,
|
104
|
+
(8192.0 / 32767.0), (16383.0 / 32767.0), (24575.0 / 32767.0), 1.0])
|
105
|
+
|
106
|
+
# Stereo 8-bit
|
107
|
+
w = WaveFile.new(:stereo, 44100, 8)
|
108
|
+
w.sample_data = [[0, 255], [32, 223], [64, 192], [96, 160], [128, 128], [160, 96], [192, 64], [223, 32], [255, 0]]
|
109
|
+
assert_equal(w.normalized_sample_data, [[-1.0, 1.0],
|
110
|
+
[-0.75, (95.0 / 127.0)],
|
111
|
+
[-0.5, (64.0 / 127.0)],
|
112
|
+
[-0.25, (32.0 / 127.0)],
|
113
|
+
[0.0, 0.0],
|
114
|
+
[(32.0 / 127.0), -0.25],
|
115
|
+
[(64.0 / 127.0), -0.5],
|
116
|
+
[(95.0 / 127.0), -0.75],
|
117
|
+
[1.0, -1.0]])
|
118
|
+
|
119
|
+
# Stereo 16-bit
|
120
|
+
w = WaveFile.new(:stereo, 44100, 16)
|
121
|
+
w.sample_data = [[-32768, 32767], [-24576, 24575], [-16384, 16384], [-8192, 8192], [0, 0], [8192, -8192], [16384, -16384], [24575, -24576], [32767, -32768]]
|
122
|
+
assert_equal(w.normalized_sample_data, [[-1.0, 1.0],
|
123
|
+
[-0.75, (24575.0 / 32767.0)],
|
124
|
+
[-0.5, (16384.0 / 32767.0)],
|
125
|
+
[-0.25, (8192.0 / 32767.0)],
|
126
|
+
[0.0, 0.0],
|
127
|
+
[(8192.0 / 32767.0), -0.25],
|
128
|
+
[(16384.0 / 32767.0), -0.5],
|
129
|
+
[(24575.0 / 32767.0), -0.75],
|
130
|
+
[1.0, -1.0]])
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_sample_data=
|
134
|
+
# Mono 8-bit
|
135
|
+
w = WaveFile.new(:mono, 44100, 8)
|
136
|
+
w.sample_data = [-1.0, -0.75, -0.5, -0.25, 0.0, 0.25, 0.5, 0.75, 1.0]
|
137
|
+
assert_equal(w.sample_data, [0, 32, 64, 96, 128, 160, 192, 223, 255])
|
138
|
+
|
139
|
+
# Mono 16-bit
|
140
|
+
w = WaveFile.new(:mono, 44100, 16)
|
141
|
+
w.sample_data = [-1.0, -0.75, -0.5, -0.25, 0.0, 0.25, 0.5, 0.75, 1.0]
|
142
|
+
assert_equal(w.sample_data, [-32768, -24576, -16384, -8192, 0, 8192, 16384, 24575, 32767])
|
143
|
+
|
144
|
+
# Stereo 8-bit
|
145
|
+
w = WaveFile.new(:stereo, 44100, 8)
|
146
|
+
w.sample_data = [[-1.0, 1.0], [-0.75, 0.75], [-0.5, 0.5], [-0.25, 0.25], [0.0, 0.0],
|
147
|
+
[0.25, -0.25], [0.5, -0.5], [0.75, -0.75], [1.0, -1.0]]
|
148
|
+
assert_equal(w.sample_data, [[0, 255], [32, 223], [64, 192], [96, 160], [128, 128],
|
149
|
+
[160, 96], [192, 64], [223, 32], [255, 0]])
|
150
|
+
|
151
|
+
# Stereo 16-bit
|
152
|
+
w = WaveFile.new(:stereo, 44100, 16)
|
153
|
+
w.sample_data = [[-1.0, 1.0], [-0.75, 0.75], [-0.5, 0.5], [-0.25, 0.25], [0.0, 0.0],
|
154
|
+
[0.25, -0.25], [0.5, -0.5], [0.75, -0.75], [1.0, -1.0]]
|
155
|
+
assert_equal(w.sample_data, [[-32768, 32767], [-24576, 24575], [-16384, 16384], [-8192, 8192], [0, 0],
|
156
|
+
[8192, -8192], [16384, -16384], [24575, -24576], [32767, -32768]])
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_mono?
|
160
|
+
w = WaveFile.new(1, 44100, 16)
|
161
|
+
assert_equal(w.mono?, true)
|
162
|
+
|
163
|
+
w = WaveFile.open("examples/valid/sine-mono-8bit.wav")
|
164
|
+
assert_equal(w.mono?, true)
|
165
|
+
|
166
|
+
w = WaveFile.new(2, 44100, 16)
|
167
|
+
assert_equal(w.mono?, false)
|
168
|
+
|
169
|
+
w = WaveFile.new(4, 44100, 16)
|
170
|
+
assert_equal(w.mono?, false)
|
171
|
+
end
|
172
|
+
|
173
|
+
def test_stereo?
|
174
|
+
w = WaveFile.new(1, 44100, 16)
|
175
|
+
assert_equal(w.stereo?, false)
|
176
|
+
|
177
|
+
w = WaveFile.open("examples/valid/sine-mono-8bit.wav")
|
178
|
+
assert_equal(w.stereo?, false)
|
179
|
+
|
180
|
+
w = WaveFile.new(2, 44100, 16)
|
181
|
+
assert_equal(w.stereo?, true)
|
182
|
+
|
183
|
+
w = WaveFile.new(4, 44100, 16)
|
184
|
+
assert_equal(w.stereo?, false)
|
185
|
+
end
|
186
|
+
|
187
|
+
def test_reverse
|
188
|
+
# Mono
|
189
|
+
w = WaveFile.new(:mono, 44100, 16)
|
190
|
+
w.sample_data = [1, 2, 3, 4, 5]
|
191
|
+
w.reverse
|
192
|
+
assert_equal(w.sample_data, [5, 4, 3, 2, 1])
|
193
|
+
|
194
|
+
# Stereo
|
195
|
+
w = WaveFile.new(:stereo, 44100, 16)
|
196
|
+
w.sample_data = [[1, 9], [2, 8], [3, 7], [4, 6], [5, 5]]
|
197
|
+
w.reverse
|
198
|
+
assert_equal(w.sample_data, [[5, 5], [4, 6], [3, 7], [2, 8], [1, 9]])
|
199
|
+
end
|
200
|
+
|
201
|
+
def test_duration()
|
202
|
+
sample_rate = 44100
|
203
|
+
|
204
|
+
[30001, 22050, 44100].each {|bits_per_sample|
|
205
|
+
[8, 16].each {|bits_per_sample|
|
206
|
+
[:mono, :stereo].each {|num_channels|
|
207
|
+
w = WaveFile.new(num_channels, sample_rate, bits_per_sample)
|
208
|
+
|
209
|
+
w.sample_data = []
|
210
|
+
assert_equal(w.duration, {:hours => 0, :minutes => 0, :seconds => 0, :milliseconds => 0})
|
211
|
+
w.sample_data = get_duration_test_samples(num_channels, (sample_rate.to_f / 1000.0).floor)
|
212
|
+
assert_equal(w.duration, {:hours => 0, :minutes => 0, :seconds => 0, :milliseconds => 0})
|
213
|
+
w.sample_data = get_duration_test_samples(num_channels, (sample_rate.to_f / 1000.0).ceil)
|
214
|
+
assert_equal(w.duration, {:hours => 0, :minutes => 0, :seconds => 0, :milliseconds => 1})
|
215
|
+
w.sample_data = get_duration_test_samples(num_channels, sample_rate / 2)
|
216
|
+
assert_equal(w.duration, {:hours => 0, :minutes => 0, :seconds => 0, :milliseconds => 500})
|
217
|
+
w.sample_data = get_duration_test_samples(num_channels, sample_rate - 1)
|
218
|
+
assert_equal(w.duration, {:hours => 0, :minutes => 0, :seconds => 0, :milliseconds => 999})
|
219
|
+
w.sample_data = get_duration_test_samples(num_channels, sample_rate)
|
220
|
+
assert_equal(w.duration, {:hours => 0, :minutes => 0, :seconds => 1, :milliseconds => 0})
|
221
|
+
w.sample_data = get_duration_test_samples(num_channels, sample_rate * 2)
|
222
|
+
assert_equal(w.duration, {:hours => 0, :minutes => 0, :seconds => 2, :milliseconds => 0})
|
223
|
+
w.sample_data = get_duration_test_samples(num_channels, (sample_rate / 2) * 3)
|
224
|
+
assert_equal(w.duration, {:hours => 0, :minutes => 0, :seconds => 1, :milliseconds => 500})
|
225
|
+
|
226
|
+
# These tests currently take too long to run...
|
227
|
+
#w.sample_data = [].fill(0.0, 0, sample_rate * 60)
|
228
|
+
#assert_equal(w.duration, {:hours => 0, :minutes => 1, :seconds => 0, :milliseconds => 0})
|
229
|
+
#w.sample_data = [].fill(0.0, 0, sample_rate * 60 * 60)
|
230
|
+
#assert_equal(w.duration, {:hours => 1, :minutes => 0, :seconds => 0, :milliseconds => 0})
|
231
|
+
}
|
232
|
+
}
|
233
|
+
}
|
234
|
+
end
|
235
|
+
|
236
|
+
def get_duration_test_samples(num_channels, num_samples)
|
237
|
+
if num_channels == :mono || num_channels == 1
|
238
|
+
return [].fill(0.0, 0, num_samples)
|
239
|
+
elsif num_channels == :stereo || num_channels == 2
|
240
|
+
return [].fill([0.0, 0.0], 0, num_samples)
|
241
|
+
else
|
242
|
+
return "error"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def test_bits_per_sample=()
|
247
|
+
# Set bits_per_sample to invalid value (non-8 or non-16)
|
248
|
+
w = WaveFile.open("examples/valid/sine-mono-8bit.wav")
|
249
|
+
assert_raise(StandardError) { w.bits_per_sample = 20 }
|
250
|
+
w = WaveFile.new(:mono, 44100, 16)
|
251
|
+
assert_raise(StandardError) { w.bits_per_sample = 4 }
|
252
|
+
|
253
|
+
w_before = WaveFile.open("examples/valid/sine-mono-8bit.wav")
|
254
|
+
w_after = WaveFile.open("examples/valid/sine-mono-8bit.wav")
|
255
|
+
w_after.bits_per_sample = 8
|
256
|
+
assert_equal(w_before.sample_data, w_after.sample_data)
|
257
|
+
|
258
|
+
w_before = WaveFile.open("examples/valid/sine-stereo-8bit.wav")
|
259
|
+
w_after = WaveFile.open("examples/valid/sine-stereo-8bit.wav")
|
260
|
+
w_after.bits_per_sample = 8
|
261
|
+
assert_equal(w_before.sample_data, w_after.sample_data)
|
262
|
+
|
263
|
+
# Open mono 16 bit file, change to 16 bit, still the same
|
264
|
+
# Open stereo 16 bit file, change to 16 bit, still the same
|
265
|
+
|
266
|
+
# Open mono 8 bit file, convert to 16 bit
|
267
|
+
w = WaveFile.new(:mono, 44100, 8)
|
268
|
+
w.sample_data = [0, 32, 64, 96, 128, 160, 192, 223, 255]
|
269
|
+
w.bits_per_sample = 16
|
270
|
+
assert_equal(w.sample_data, [-32768, -24576, -16384, -8192, 0, 8256, 16513, 24511, 32767])
|
271
|
+
|
272
|
+
# Open stereo 8 bit file, convert to 16 bit
|
273
|
+
w = WaveFile.new(:stereo, 44100, 8)
|
274
|
+
w.sample_data = [[0, 255], [32, 223], [64, 192], [96, 160], [128, 128],
|
275
|
+
[160, 96], [192, 64], [223, 32], [255, 0]]
|
276
|
+
w.bits_per_sample = 16
|
277
|
+
assert_equal(w.sample_data, [[-32768, 32767], [-24576, 24511], [-16384, 16513], [-8192, 8256], [0, 0],
|
278
|
+
[8256, -8192], [16513, -16384], [24511, -24576], [32767, -32768]])
|
279
|
+
|
280
|
+
# Open mono 16 bit file, convert to 8 bit
|
281
|
+
w = WaveFile.new(:mono, 44100, 16)
|
282
|
+
w.sample_data = [-32768, -24576, -16384, -8192, 0, 8256, 16513, 24511, 32767]
|
283
|
+
w.bits_per_sample = 8
|
284
|
+
assert_equal(w.sample_data, [0, 32, 64, 96, 128, 160, 192, 223, 255])
|
285
|
+
|
286
|
+
# Open stereo 16 bit file, convert to 8 bit, conversion successful
|
287
|
+
w = WaveFile.new(:stereo, 44100, 16)
|
288
|
+
w.sample_data = [[-32768, 32767], [-24576, 24511], [-16384, 16513], [-8192, 8256], [0, 0],
|
289
|
+
[8256, -8192], [16513, -16384], [24511, -24576], [32767, -32768]]
|
290
|
+
w.bits_per_sample = 8
|
291
|
+
assert_equal(w.sample_data, [[0, 255], [32, 223], [64, 192], [96, 160], [128, 128],
|
292
|
+
[160, 96], [192, 64], [223, 32], [255, 0]])
|
293
|
+
|
294
|
+
# Open 8 bit mono, convert to 16 bit, back to 8 bit.
|
295
|
+
w_before = WaveFile.open("examples/valid/sine-mono-8bit.wav")
|
296
|
+
w_after = WaveFile.open("examples/valid/sine-mono-8bit.wav")
|
297
|
+
w_after.bits_per_sample = 16
|
298
|
+
assert_not_equal(w_before.sample_data, w_after.sample_data)
|
299
|
+
w_after.bits_per_sample = 8
|
300
|
+
assert_equal(w_before.sample_data, w_after.sample_data)
|
301
|
+
|
302
|
+
# Open 8 bit stereo, convert to 16 bit, back to 8 bit.
|
303
|
+
w_before = WaveFile.open("examples/valid/sine-stereo-8bit.wav")
|
304
|
+
w_after = WaveFile.open("examples/valid/sine-stereo-8bit.wav")
|
305
|
+
w_after.bits_per_sample = 16
|
306
|
+
assert_not_equal(w_before.sample_data, w_after.sample_data)
|
307
|
+
w_after.bits_per_sample = 8
|
308
|
+
assert_equal(w_before.sample_data, w_after.sample_data)
|
309
|
+
|
310
|
+
# Open 16 bit mono, convert to 8 bit, back to 16 bit.
|
311
|
+
# Open 16 bit stereo, convert to 8 bit, back to 16 bit.
|
312
|
+
end
|
313
|
+
|
314
|
+
def test_num_channels=()
|
315
|
+
w = WaveFile.new(:mono, 44100, 16)
|
316
|
+
w.sample_data = [-32768, -24576, -16384, -8192, 0, 8256, 16513, 24511, 32767]
|
317
|
+
w.num_channels = 2
|
318
|
+
assert_equal(w.sample_data, [[-32768, -32768], [-24576, -24576], [-16384, -16384], [-8192, -8192], [0, 0],
|
319
|
+
[8256, 8256], [16513, 16513], [24511, 24511], [32767, 32767]])
|
320
|
+
|
321
|
+
w = WaveFile.new(:mono, 44100, 16)
|
322
|
+
w.sample_data = [-32768, -24576, -16384, -8192, 0, 8256, 16513, 24511, 32767]
|
323
|
+
w.num_channels = 3
|
324
|
+
assert_equal(w.sample_data, [[-32768, -32768, -32768], [-24576, -24576, -24576], [-16384, -16384, -16384], [-8192, -8192, -8192], [0, 0, 0],
|
325
|
+
[8256, 8256, 8256], [16513, 16513, 16513], [24511, 24511, 24511], [32767, 32767, 32767]])
|
326
|
+
|
327
|
+
w = WaveFile.new(:stereo, 44100, 16)
|
328
|
+
w.sample_data = [[-32768, -32768], [-24576, -24576], [-16384, -16384], [-8192, -8192], [0, 0],
|
329
|
+
[8256, 8256], [16513, 16513], [24511, 24511], [32767, 32767]]
|
330
|
+
w.num_channels = 1
|
331
|
+
assert_equal(w.sample_data, [-32768, -24576, -16384, -8192, 0, 8256, 16513, 24511, 32767])
|
332
|
+
|
333
|
+
w = WaveFile.new(3, 44100, 16)
|
334
|
+
w.sample_data = [[-32768, -32768, -32768], [-24576, -24576, -24576], [-16384, -16384, -16384], [-8192, -8192, -8192], [0, 0, 0],
|
335
|
+
[8256, 8256, 8256], [16513, 16513, 16513], [24511, 24511, 24511], [32767, 32767, 32767]]
|
336
|
+
w.num_channels = 1
|
337
|
+
assert_equal(w.sample_data, [-32768, -24576, -16384, -8192, 0, 8256, 16513, 24511, 32767])
|
338
|
+
end
|
339
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wavefile
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joel Strait
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-01-12 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: joel.strait at gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- LICENSE
|
26
|
+
- README.markdown
|
27
|
+
- lib/wavefile.rb
|
28
|
+
- test/wavefile_test.rb
|
29
|
+
has_rdoc: true
|
30
|
+
homepage: http://www.joelstrait.com/
|
31
|
+
licenses: []
|
32
|
+
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options: []
|
35
|
+
|
36
|
+
require_paths:
|
37
|
+
- lib
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: "0"
|
43
|
+
version:
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: "0"
|
49
|
+
version:
|
50
|
+
requirements: []
|
51
|
+
|
52
|
+
rubyforge_project:
|
53
|
+
rubygems_version: 1.3.5
|
54
|
+
signing_key:
|
55
|
+
specification_version: 3
|
56
|
+
summary: A class for reading and writing Wave sound files (*.wav)
|
57
|
+
test_files: []
|
58
|
+
|