wavefile 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/LICENSE +1 -1
- data/README.markdown +71 -18
- data/lib/wavefile/buffer.rb +63 -39
- data/lib/wavefile/duration.rb +34 -0
- data/lib/wavefile/format.rb +42 -16
- data/lib/wavefile/info.rb +5 -38
- data/lib/wavefile/reader.rb +88 -61
- data/lib/wavefile/writer.rb +57 -25
- data/lib/wavefile.rb +6 -4
- data/test/buffer_test.rb +227 -37
- data/test/duration_test.rb +73 -0
- data/test/fixtures/actual_output/{valid_mono_8_44100_with_padding_byte.wav → valid_mono_pcm_8_44100_with_padding_byte.wav} +0 -0
- data/test/fixtures/{expected_output → valid}/no_samples.wav +0 -0
- data/test/fixtures/valid/valid_mono_float_32_44100.wav +0 -0
- data/test/fixtures/valid/valid_mono_float_64_44100.wav +0 -0
- data/test/fixtures/{expected_output/valid_mono_16_44100.wav → valid/valid_mono_pcm_16_44100.wav} +0 -0
- data/test/fixtures/{expected_output/valid_mono_32_44100.wav → valid/valid_mono_pcm_32_44100.wav} +0 -0
- data/test/fixtures/{expected_output/valid_mono_8_44100.wav → valid/valid_mono_pcm_8_44100.wav} +0 -0
- data/test/fixtures/{expected_output/valid_mono_8_44100_with_padding_byte.wav → valid/valid_mono_pcm_8_44100_with_padding_byte.wav} +0 -0
- data/test/fixtures/valid/valid_stereo_float_32_44100.wav +0 -0
- data/test/fixtures/valid/valid_stereo_float_64_44100.wav +0 -0
- data/test/fixtures/{expected_output/valid_stereo_16_44100.wav → valid/valid_stereo_pcm_16_44100.wav} +0 -0
- data/test/fixtures/{expected_output/valid_stereo_32_44100.wav → valid/valid_stereo_pcm_32_44100.wav} +0 -0
- data/test/fixtures/{expected_output/valid_stereo_8_44100.wav → valid/valid_stereo_pcm_8_44100.wav} +0 -0
- data/test/fixtures/valid/valid_tri_float_32_44100.wav +0 -0
- data/test/fixtures/valid/valid_tri_float_64_44100.wav +0 -0
- data/test/fixtures/{expected_output/valid_tri_16_44100.wav → valid/valid_tri_pcm_16_44100.wav} +0 -0
- data/test/fixtures/{expected_output/valid_tri_32_44100.wav → valid/valid_tri_pcm_32_44100.wav} +0 -0
- data/test/fixtures/{expected_output/valid_tri_8_44100.wav → valid/valid_tri_pcm_8_44100.wav} +0 -0
- data/test/format_test.rb +88 -58
- data/test/info_test.rb +9 -37
- data/test/reader_test.rb +160 -63
- data/test/wavefile_io_test_helper.rb +40 -30
- data/test/writer_test.rb +124 -37
- metadata +29 -31
- data/test/fixtures/valid/valid_mono_16_44100.wav +0 -0
- data/test/fixtures/valid/valid_mono_32_44100.wav +0 -0
- data/test/fixtures/valid/valid_mono_8_44100.wav +0 -0
- data/test/fixtures/valid/valid_mono_8_44100_with_padding_byte.wav +0 -0
- data/test/fixtures/valid/valid_stereo_16_44100.wav +0 -0
- data/test/fixtures/valid/valid_stereo_32_44100.wav +0 -0
- data/test/fixtures/valid/valid_stereo_8_44100.wav +0 -0
- data/test/fixtures/valid/valid_tri_16_44100.wav +0 -0
- data/test/fixtures/valid/valid_tri_32_44100.wav +0 -0
- data/test/fixtures/valid/valid_tri_8_44100.wav +0 -0
data/lib/wavefile/reader.rb
CHANGED
@@ -9,7 +9,7 @@ module WaveFile
|
|
9
9
|
|
10
10
|
|
11
11
|
# Provides the ability to read sample data out of a wave file, as well as query a
|
12
|
-
# wave file about its metadata (e.g. number of channels, sample rate, etc).
|
12
|
+
# wave file about its metadata (e.g. number of channels, sample rate, etc).
|
13
13
|
class Reader
|
14
14
|
# Returns a Reader object that is ready to start reading the specified file's sample data.
|
15
15
|
#
|
@@ -26,35 +26,34 @@ module WaveFile
|
|
26
26
|
@file_name = file_name
|
27
27
|
@file = File.open(file_name, "rb")
|
28
28
|
|
29
|
-
raw_format_chunk,
|
30
|
-
@
|
31
|
-
@
|
29
|
+
raw_format_chunk, sample_frame_count = HeaderReader.new(@file, @file_name).read_until_data_chunk
|
30
|
+
@current_sample_frame = 0
|
31
|
+
@total_sample_frames = sample_frame_count
|
32
32
|
|
33
33
|
# Make file is in a format we can actually read
|
34
34
|
validate_format_chunk(raw_format_chunk)
|
35
35
|
|
36
|
+
native_sample_format = "#{FORMAT_CODES.invert[raw_format_chunk[:audio_format]]}_#{raw_format_chunk[:bits_per_sample]}".to_sym
|
36
37
|
@native_format = Format.new(raw_format_chunk[:channels],
|
37
|
-
|
38
|
+
native_sample_format,
|
38
39
|
raw_format_chunk[:sample_rate])
|
39
|
-
@pack_code = PACK_CODES[@native_format.bits_per_sample]
|
40
|
-
|
41
|
-
if format == nil
|
42
|
-
@format = @native_format
|
43
|
-
else
|
44
|
-
@format = format
|
45
|
-
end
|
40
|
+
@pack_code = PACK_CODES[@native_format.sample_format][@native_format.bits_per_sample]
|
41
|
+
@format = (format == nil) ? @native_format : format
|
46
42
|
|
47
43
|
if block_given?
|
48
|
-
|
49
|
-
|
44
|
+
begin
|
45
|
+
yield(self)
|
46
|
+
ensure
|
47
|
+
close
|
48
|
+
end
|
50
49
|
end
|
51
50
|
end
|
52
51
|
|
53
52
|
|
54
53
|
# Reads metadata from the specified wave file and returns an Info object with the results.
|
55
|
-
# Metadata includes things like the number of channels, bits per sample, number of
|
56
|
-
# sample encoding format (i.e. PCM, IEEE float, uLaw etc). See the Info object for
|
57
|
-
# detail on exactly what metadata is available.
|
54
|
+
# Metadata includes things like the number of channels, bits per sample, number of sample
|
55
|
+
# frames, sample encoding format (i.e. PCM, IEEE float, uLaw etc). See the Info object for
|
56
|
+
# more detail on exactly what metadata is available.
|
58
57
|
#
|
59
58
|
# file_name - The name of the wave file to read from
|
60
59
|
#
|
@@ -68,84 +67,85 @@ module WaveFile
|
|
68
67
|
# that WaveFile can't read.
|
69
68
|
def self.info(file_name)
|
70
69
|
file = File.open(file_name, "rb")
|
71
|
-
raw_format_chunk,
|
72
|
-
file.close
|
70
|
+
raw_format_chunk, sample_frame_count = HeaderReader.new(file, file_name).read_until_data_chunk
|
71
|
+
file.close
|
73
72
|
|
74
|
-
|
73
|
+
Info.new(file_name, raw_format_chunk, sample_frame_count)
|
75
74
|
end
|
76
75
|
|
77
76
|
|
78
77
|
# Reads sample data of the into successive Buffers of the specified size, until there is no more
|
79
78
|
# sample data to be read. When all sample data has been read, the Reader is automatically closed.
|
80
|
-
# Each Buffer is passed to the
|
79
|
+
# Each Buffer is passed to the given block.
|
81
80
|
#
|
82
|
-
# Note that the number of
|
81
|
+
# Note that sample_frame_count indicates the number of sample frames to read, not number of samples.
|
82
|
+
# A sample frame include one sample for each channel. For example, if sample_frame_count is 1024, then
|
83
83
|
# for a stereo file 1024 samples will be read from the left channel, and 1024 samples will be read from
|
84
84
|
# the right channel.
|
85
85
|
#
|
86
|
-
#
|
87
|
-
#
|
88
|
-
#
|
86
|
+
# sample_frame_count - The number of sample frames to read into each Buffer from each channel. The number
|
87
|
+
# of sample frames read into the final Buffer could be less than this size, if there
|
88
|
+
# are not enough remaining.
|
89
89
|
#
|
90
90
|
# Returns nothing.
|
91
|
-
def each_buffer(
|
91
|
+
def each_buffer(sample_frame_count)
|
92
92
|
begin
|
93
93
|
while true do
|
94
|
-
yield(read(
|
94
|
+
yield(read(sample_frame_count))
|
95
95
|
end
|
96
96
|
rescue EOFError
|
97
|
-
close
|
97
|
+
close
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
101
101
|
|
102
|
-
# Reads the specified number of
|
103
|
-
# at most
|
102
|
+
# Reads the specified number of sample frames from the wave file into a Buffer. Note that the Buffer will have
|
103
|
+
# at most sample_frame_count sample frames, but could have less if the file doesn't have enough remaining.
|
104
104
|
#
|
105
|
-
#
|
106
|
-
#
|
105
|
+
# sample_frame_count - The number of sample frames to read. Note that each sample frame includes a sample for
|
106
|
+
# each channel.
|
107
107
|
#
|
108
|
-
# Returns a Buffer containing
|
108
|
+
# Returns a Buffer containing sample_frame_count sample frames
|
109
109
|
# Raises EOFError if no samples could be read due to reaching the end of the file
|
110
|
-
def read(
|
111
|
-
if @
|
110
|
+
def read(sample_frame_count)
|
111
|
+
if @current_sample_frame >= @total_sample_frames
|
112
112
|
#FIXME: Do something different here, because the end of the file has not actually necessarily been reached
|
113
113
|
raise EOFError
|
114
|
-
elsif
|
115
|
-
|
114
|
+
elsif sample_frame_count > sample_frames_remaining
|
115
|
+
sample_frame_count = sample_frames_remaining
|
116
116
|
end
|
117
117
|
|
118
|
-
samples = @file.sysread(
|
119
|
-
@
|
118
|
+
samples = @file.sysread(sample_frame_count * @native_format.block_align).unpack(@pack_code)
|
119
|
+
@current_sample_frame += sample_frame_count
|
120
120
|
|
121
121
|
if @native_format.channels > 1
|
122
122
|
num_multichannel_samples = samples.length / @native_format.channels
|
123
123
|
multichannel_data = Array.new(num_multichannel_samples)
|
124
|
-
|
124
|
+
|
125
125
|
if(@native_format.channels == 2)
|
126
126
|
# Files with more than 2 channels are expected to be less common, so if there are 2 channels
|
127
127
|
# using a faster specific algorithm instead of a general one.
|
128
|
-
num_multichannel_samples.times {|i| multichannel_data[i] = [samples.pop
|
128
|
+
num_multichannel_samples.times {|i| multichannel_data[i] = [samples.pop, samples.pop].reverse! }
|
129
129
|
else
|
130
130
|
# General algorithm that works for any number of channels, 2 or greater.
|
131
131
|
num_multichannel_samples.times do |i|
|
132
132
|
sample = Array.new(@native_format.channels)
|
133
|
-
@native_format.channels.times {|j| sample[j] = samples.pop
|
134
|
-
multichannel_data[i] = sample.reverse!
|
133
|
+
@native_format.channels.times {|j| sample[j] = samples.pop }
|
134
|
+
multichannel_data[i] = sample.reverse!
|
135
135
|
end
|
136
136
|
end
|
137
137
|
|
138
|
-
samples = multichannel_data.reverse!
|
138
|
+
samples = multichannel_data.reverse!
|
139
139
|
end
|
140
140
|
|
141
141
|
buffer = Buffer.new(samples, @native_format)
|
142
|
-
|
142
|
+
buffer.convert(@format)
|
143
143
|
end
|
144
144
|
|
145
145
|
|
146
146
|
# Returns true if the Reader is closed, and false if it is open and available for reading.
|
147
|
-
def closed?
|
148
|
-
|
147
|
+
def closed?
|
148
|
+
@file.closed?
|
149
149
|
end
|
150
150
|
|
151
151
|
|
@@ -153,26 +153,53 @@ module WaveFile
|
|
153
153
|
#
|
154
154
|
# Returns nothing.
|
155
155
|
# Raises IOError if the Reader is already closed.
|
156
|
-
def close
|
157
|
-
@file.close
|
156
|
+
def close
|
157
|
+
@file.close
|
158
|
+
end
|
159
|
+
|
160
|
+
# Returns a Duration instance for the total number of sample frames in the file
|
161
|
+
def total_duration
|
162
|
+
Duration.new(total_sample_frames, @format.sample_rate)
|
158
163
|
end
|
159
164
|
|
160
|
-
|
165
|
+
# Returns the name of the Wave file that is being read
|
166
|
+
attr_reader :file_name
|
167
|
+
|
168
|
+
# Returns a Format object describing how sample data is being read from the Wave file (number of
|
169
|
+
# channels, sample format and bits per sample, etc). Note that this might be different from the
|
170
|
+
# underlying format of the Wave file on disk.
|
171
|
+
attr_reader :format
|
172
|
+
|
173
|
+
# Returns the index of the sample frame which is "cued up" for reading. I.e., the index
|
174
|
+
# of the next sample frame that will be read. A sample frame contains a single sample
|
175
|
+
# for each channel. So if there are 1,000 sample frames in a stereo file, this means
|
176
|
+
# there are 1,000 left-channel samples and 1,000 right-channel samples.
|
177
|
+
attr_reader :current_sample_frame
|
178
|
+
|
179
|
+
# Returns the total number of sample frames in the file. A sample frame contains a single
|
180
|
+
# sample for each channel. So if there are 1,000 sample frames in a stereo file, this means
|
181
|
+
# there are 1,000 left-channel samples and 1,000 right-channel samples.
|
182
|
+
attr_reader :total_sample_frames
|
161
183
|
|
162
184
|
private
|
163
185
|
|
186
|
+
# The number of sample frames in the file after the current sample frame
|
187
|
+
def sample_frames_remaining
|
188
|
+
@total_sample_frames - @current_sample_frame
|
189
|
+
end
|
190
|
+
|
164
191
|
def validate_format_chunk(raw_format_chunk)
|
165
192
|
# :byte_rate and :block_align are not checked to make sure that match :channels/:sample_rate/bits_per_sample
|
166
193
|
# because this library doesn't use them.
|
167
194
|
|
168
|
-
unless raw_format_chunk[:audio_format]
|
195
|
+
unless FORMAT_CODES.values.include? raw_format_chunk[:audio_format]
|
169
196
|
raise UnsupportedFormatError, "Audio format is #{raw_format_chunk[:audio_format]}, " +
|
170
|
-
"but only format code 1 (
|
197
|
+
"but only format code 1 (PCM) or 3 (floating point) is supported."
|
171
198
|
end
|
172
199
|
|
173
|
-
unless Format::SUPPORTED_BITS_PER_SAMPLE.include?(raw_format_chunk[:bits_per_sample])
|
200
|
+
unless Format::SUPPORTED_BITS_PER_SAMPLE[FORMAT_CODES.invert[raw_format_chunk[:audio_format]]].include?(raw_format_chunk[:bits_per_sample])
|
174
201
|
raise UnsupportedFormatError, "Bits per sample is #{raw_format_chunk[:bits_per_sample]}, " +
|
175
|
-
"but only #{Format::SUPPORTED_BITS_PER_SAMPLE.inspect} are supported."
|
202
|
+
"but only #{Format::SUPPORTED_BITS_PER_SAMPLE[:pcm].inspect} are supported."
|
176
203
|
end
|
177
204
|
|
178
205
|
unless raw_format_chunk[:channels] > 0
|
@@ -200,8 +227,8 @@ module WaveFile
|
|
200
227
|
@file_name = file_name
|
201
228
|
end
|
202
229
|
|
203
|
-
def read_until_data_chunk
|
204
|
-
read_riff_chunk
|
230
|
+
def read_until_data_chunk
|
231
|
+
read_riff_chunk
|
205
232
|
|
206
233
|
begin
|
207
234
|
chunk_id = @file.sysread(4)
|
@@ -225,14 +252,14 @@ module WaveFile
|
|
225
252
|
raise_error InvalidFormatError, "The format chunk is either missing, or it comes after the data chunk."
|
226
253
|
end
|
227
254
|
|
228
|
-
|
255
|
+
sample_frame_count = chunk_size / format_chunk[:block_align]
|
229
256
|
|
230
|
-
return format_chunk,
|
257
|
+
return format_chunk, sample_frame_count
|
231
258
|
end
|
232
259
|
|
233
260
|
private
|
234
261
|
|
235
|
-
def read_riff_chunk
|
262
|
+
def read_riff_chunk
|
236
263
|
riff_header = {}
|
237
264
|
riff_header[:chunk_id],
|
238
265
|
riff_header[:chunk_size],
|
@@ -246,7 +273,7 @@ module WaveFile
|
|
246
273
|
raise_error InvalidFormatError, "Expected RIFF format of '#{WAVEFILE_FORMAT_CODE}', but was '#{riff_header[:riff_format]}'"
|
247
274
|
end
|
248
275
|
|
249
|
-
|
276
|
+
riff_header
|
250
277
|
end
|
251
278
|
|
252
279
|
def read_format_chunk(chunk_id, chunk_size)
|
@@ -278,7 +305,7 @@ module WaveFile
|
|
278
305
|
# TODO: Parse the extension
|
279
306
|
end
|
280
307
|
|
281
|
-
|
308
|
+
format_chunk
|
282
309
|
end
|
283
310
|
|
284
311
|
def read_chunk_body(chunk_id, chunk_size)
|
data/lib/wavefile/writer.rb
CHANGED
@@ -13,56 +13,61 @@ module WaveFile
|
|
13
13
|
# starts, assuming this canonical format:
|
14
14
|
#
|
15
15
|
# RIFF Chunk Header (12 bytes)
|
16
|
-
# Format Chunk (
|
16
|
+
# Format Chunk (16 bytes for PCM, 18 bytes for floating point)
|
17
|
+
# FACT Chunk (0 bytes for PCM, 12 bytes for floating point)
|
17
18
|
# Data Chunk Header (8 bytes)
|
18
19
|
#
|
19
20
|
# All wave files written by Writer use this canonical format.
|
20
|
-
CANONICAL_HEADER_BYTE_LENGTH = 36
|
21
|
+
CANONICAL_HEADER_BYTE_LENGTH = {:pcm => 36, :float => 50}
|
21
22
|
|
22
23
|
|
23
24
|
# Returns a constructed Writer object which is available for writing sample data to the specified
|
24
|
-
# file (via the write
|
25
|
+
# file (via the write method). When all sample data has been written, the Writer should be closed.
|
25
26
|
# Note that the wave file being written to will NOT be valid (and playable in other programs) until
|
26
27
|
# the Writer has been closed.
|
27
28
|
#
|
28
29
|
# If a block is given to this method, sample data can be written inside the given block. When the
|
29
30
|
# block terminates, the Writer will be automatically closed (and no more sample data can be written).
|
30
31
|
#
|
31
|
-
# If no block is given, then sample data can be written until the close
|
32
|
+
# If no block is given, then sample data can be written until the close method is called.
|
32
33
|
def initialize(file_name, format)
|
34
|
+
@file_name = file_name
|
33
35
|
@file = File.open(file_name, "wb")
|
34
36
|
@format = format
|
35
37
|
|
36
|
-
@
|
37
|
-
@pack_code = PACK_CODES[format.bits_per_sample]
|
38
|
+
@total_sample_frames = 0
|
39
|
+
@pack_code = PACK_CODES[format.sample_format][format.bits_per_sample]
|
38
40
|
|
39
41
|
# Note that the correct sizes for the RIFF and data chunks can't be determined
|
40
42
|
# until all samples have been written, so this header as written will be incorrect.
|
41
|
-
# When close
|
43
|
+
# When close is called, the correct sizes will be re-written.
|
42
44
|
write_header(0)
|
43
45
|
|
44
46
|
if block_given?
|
45
|
-
|
46
|
-
|
47
|
+
begin
|
48
|
+
yield(self)
|
49
|
+
ensure
|
50
|
+
close
|
51
|
+
end
|
47
52
|
end
|
48
53
|
end
|
49
54
|
|
50
55
|
|
51
56
|
# Appends the sample data in the given Buffer to the end of the wave file.
|
52
57
|
#
|
53
|
-
# Returns the number of sample that have been written to the file so far.
|
58
|
+
# Returns the number of sample frames that have been written to the file so far.
|
54
59
|
# Raises IOError if the Writer has been closed.
|
55
60
|
def write(buffer)
|
56
61
|
samples = buffer.convert(@format).samples
|
57
62
|
|
58
63
|
@file.syswrite(samples.flatten.pack(@pack_code))
|
59
|
-
@
|
64
|
+
@total_sample_frames += samples.length
|
60
65
|
end
|
61
66
|
|
62
67
|
|
63
68
|
# Returns true if the Writer is closed, and false if it is open and available for writing.
|
64
|
-
def closed?
|
65
|
-
|
69
|
+
def closed?
|
70
|
+
@file.closed?
|
66
71
|
end
|
67
72
|
|
68
73
|
|
@@ -70,17 +75,19 @@ module WaveFile
|
|
70
75
|
#
|
71
76
|
# Note that the wave file will NOT be valid until this method is called. The wave file
|
72
77
|
# format requires certain information about the amount of sample data, and this can't be
|
73
|
-
# determined until all samples have been written.
|
78
|
+
# determined until all samples have been written. (This method doesn't need to be called
|
79
|
+
# when passing a block to Writer.new, as this method will automatically be called when
|
80
|
+
# the block exits).
|
74
81
|
#
|
75
82
|
# Returns nothing.
|
76
83
|
# Raises IOError if the Writer is already closed.
|
77
|
-
def close
|
84
|
+
def close
|
78
85
|
# The RIFF specification requires that each chunk be aligned to an even number of bytes,
|
79
86
|
# even if the byte count is an odd number. Therefore if an odd number of bytes has been
|
80
87
|
# written, write an empty padding byte.
|
81
88
|
#
|
82
89
|
# See http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf, page 11.
|
83
|
-
bytes_written = @
|
90
|
+
bytes_written = @total_sample_frames * @format.block_align
|
84
91
|
if bytes_written.odd?
|
85
92
|
@file.syswrite(EMPTY_BYTE)
|
86
93
|
end
|
@@ -89,34 +96,59 @@ module WaveFile
|
|
89
96
|
# samples have been written, so go back to the beginning of the file and re-write
|
90
97
|
# those chunk headers with the correct sizes.
|
91
98
|
@file.sysseek(0)
|
92
|
-
write_header(@
|
93
|
-
|
94
|
-
@file.close
|
99
|
+
write_header(@total_sample_frames)
|
100
|
+
|
101
|
+
@file.close
|
102
|
+
end
|
103
|
+
|
104
|
+
# Returns a Duration instance for the number of sample frames that have been written so far
|
105
|
+
def total_duration
|
106
|
+
Duration.new(@total_sample_frames, @format.sample_rate)
|
95
107
|
end
|
96
108
|
|
97
|
-
|
109
|
+
# Returns the name of the Wave file that is being written to
|
110
|
+
attr_reader :file_name
|
111
|
+
|
112
|
+
# Returns a Format object describing the Wave file being written (number of channels, sample
|
113
|
+
# format and bits per sample, sample rate, etc.)
|
114
|
+
attr_reader :format
|
115
|
+
|
116
|
+
# Returns the number of samples (per channel) that have been written to the file so far.
|
117
|
+
# For example, if 1000 "left" samples and 1000 "right" samples have been written to a stereo file,
|
118
|
+
# this will return 1000.
|
119
|
+
attr_reader :total_sample_frames
|
98
120
|
|
99
121
|
private
|
100
122
|
|
101
123
|
# Writes the RIFF chunk header, format chunk, and the header for the data chunk. After this
|
102
124
|
# method is called the file will be "queued up" and ready for writing actual sample data.
|
103
|
-
def write_header(
|
104
|
-
sample_data_byte_count =
|
125
|
+
def write_header(sample_frame_count)
|
126
|
+
sample_data_byte_count = sample_frame_count * @format.block_align
|
105
127
|
|
106
128
|
# Write the header for the RIFF chunk
|
107
129
|
header = CHUNK_IDS[:riff]
|
108
|
-
header += [CANONICAL_HEADER_BYTE_LENGTH + sample_data_byte_count].pack(UNSIGNED_INT_32)
|
130
|
+
header += [CANONICAL_HEADER_BYTE_LENGTH[@format.sample_format] + sample_data_byte_count].pack(UNSIGNED_INT_32)
|
109
131
|
header += WAVEFILE_FORMAT_CODE
|
110
132
|
|
111
133
|
# Write the format chunk
|
112
134
|
header += CHUNK_IDS[:format]
|
113
|
-
header += [FORMAT_CHUNK_BYTE_LENGTH].pack(UNSIGNED_INT_32)
|
114
|
-
header += [
|
135
|
+
header += [FORMAT_CHUNK_BYTE_LENGTH[@format.sample_format]].pack(UNSIGNED_INT_32)
|
136
|
+
header += [FORMAT_CODES[@format.sample_format]].pack(UNSIGNED_INT_16)
|
115
137
|
header += [@format.channels].pack(UNSIGNED_INT_16)
|
116
138
|
header += [@format.sample_rate].pack(UNSIGNED_INT_32)
|
117
139
|
header += [@format.byte_rate].pack(UNSIGNED_INT_32)
|
118
140
|
header += [@format.block_align].pack(UNSIGNED_INT_16)
|
119
141
|
header += [@format.bits_per_sample].pack(UNSIGNED_INT_16)
|
142
|
+
if @format.sample_format == :float
|
143
|
+
header += [0].pack(UNSIGNED_INT_16)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Write the FACT chunk, if necessary
|
147
|
+
unless @format.sample_format == :pcm
|
148
|
+
header += CHUNK_IDS[:fact]
|
149
|
+
header += [4].pack(UNSIGNED_INT_32)
|
150
|
+
header += [sample_frame_count].pack(UNSIGNED_INT_32)
|
151
|
+
end
|
120
152
|
|
121
153
|
# Write the header for the data chunk
|
122
154
|
header += CHUNK_IDS[:data]
|
data/lib/wavefile.rb
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
require 'wavefile/buffer'
|
2
|
+
require 'wavefile/duration'
|
2
3
|
require 'wavefile/format'
|
3
4
|
require 'wavefile/info'
|
4
5
|
require 'wavefile/reader'
|
5
6
|
require 'wavefile/writer'
|
6
7
|
|
7
8
|
module WaveFile
|
8
|
-
VERSION = "0.
|
9
|
+
VERSION = "0.5.0"
|
9
10
|
|
10
11
|
WAVEFILE_FORMAT_CODE = "WAVE"
|
11
|
-
FORMAT_CHUNK_BYTE_LENGTH = 16
|
12
|
-
|
12
|
+
FORMAT_CHUNK_BYTE_LENGTH = {:pcm => 16, :float => 18}
|
13
|
+
FORMAT_CODES = {:pcm => 1, :float => 3}
|
13
14
|
CHUNK_IDS = {:riff => "RIFF",
|
14
15
|
:format => "fmt ",
|
15
16
|
:data => "data",
|
@@ -24,7 +25,8 @@ module WaveFile
|
|
24
25
|
:sample => "smpl",
|
25
26
|
:instrument => "inst" }
|
26
27
|
|
27
|
-
PACK_CODES = {8 => "C*", 16 => "s*", 32 => "l*"}
|
28
|
+
PACK_CODES = {:pcm => {8 => "C*", 16 => "s*", 32 => "l*"},
|
29
|
+
:float => { 32 => "e*", 64 => "E*"}}
|
28
30
|
|
29
31
|
UNSIGNED_INT_16 = "v"
|
30
32
|
UNSIGNED_INT_32 = "V"
|