wavefile 0.4.0 → 0.5.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.
- 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"
|