wavefile 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/LICENSE +1 -1
  2. data/README.markdown +29 -56
  3. data/Rakefile +6 -0
  4. data/lib/wavefile.rb +28 -452
  5. data/lib/wavefile/buffer.rb +147 -0
  6. data/lib/wavefile/format.rb +69 -0
  7. data/lib/wavefile/info.rb +53 -0
  8. data/lib/wavefile/reader.rb +296 -0
  9. data/lib/wavefile/writer.rb +128 -0
  10. data/test/buffer_test.rb +121 -0
  11. data/test/fixtures/actual_output/valid_mono_8_44100_with_padding_byte.wav +0 -0
  12. data/test/fixtures/expected_output/no_samples.wav +0 -0
  13. data/test/fixtures/expected_output/valid_mono_16_44100.wav +0 -0
  14. data/test/fixtures/expected_output/valid_mono_32_44100.wav +0 -0
  15. data/test/fixtures/expected_output/valid_mono_8_44100.wav +0 -0
  16. data/test/fixtures/expected_output/valid_mono_8_44100_with_padding_byte.wav +0 -0
  17. data/test/fixtures/expected_output/valid_stereo_16_44100.wav +0 -0
  18. data/test/fixtures/expected_output/valid_stereo_32_44100.wav +0 -0
  19. data/test/fixtures/expected_output/valid_stereo_8_44100.wav +0 -0
  20. data/test/fixtures/expected_output/valid_tri_16_44100.wav +0 -0
  21. data/test/fixtures/expected_output/valid_tri_32_44100.wav +0 -0
  22. data/test/fixtures/expected_output/valid_tri_8_44100.wav +0 -0
  23. data/test/fixtures/invalid/README.markdown +10 -0
  24. data/test/fixtures/invalid/bad_riff_header.wav +1 -0
  25. data/test/fixtures/invalid/bad_wavefile_format.wav +0 -0
  26. data/test/fixtures/invalid/empty.wav +0 -0
  27. data/test/fixtures/invalid/empty_format_chunk.wav +0 -0
  28. data/test/fixtures/invalid/incomplete_riff_header.wav +1 -0
  29. data/test/fixtures/invalid/insufficient_format_chunk.wav +0 -0
  30. data/test/fixtures/invalid/no_data_chunk.wav +0 -0
  31. data/test/fixtures/invalid/no_format_chunk.wav +0 -0
  32. data/test/fixtures/unsupported/README.markdown +6 -0
  33. data/test/fixtures/unsupported/bad_audio_format.wav +0 -0
  34. data/test/fixtures/unsupported/bad_channel_count.wav +0 -0
  35. data/test/fixtures/unsupported/bad_sample_rate.wav +0 -0
  36. data/test/fixtures/unsupported/unsupported_audio_format.wav +0 -0
  37. data/test/fixtures/unsupported/unsupported_bits_per_sample.wav +0 -0
  38. data/test/fixtures/valid/README.markdown +3 -0
  39. data/test/fixtures/valid/valid_mono_16_44100.wav +0 -0
  40. data/test/fixtures/valid/valid_mono_32_44100.wav +0 -0
  41. data/test/fixtures/valid/valid_mono_8_44100.wav +0 -0
  42. data/test/fixtures/valid/valid_mono_8_44100_with_padding_byte.wav +0 -0
  43. data/test/fixtures/valid/valid_stereo_16_44100.wav +0 -0
  44. data/test/fixtures/valid/valid_stereo_32_44100.wav +0 -0
  45. data/test/fixtures/valid/valid_stereo_8_44100.wav +0 -0
  46. data/test/fixtures/valid/valid_tri_16_44100.wav +0 -0
  47. data/test/fixtures/valid/valid_tri_32_44100.wav +0 -0
  48. data/test/fixtures/valid/valid_tri_8_44100.wav +0 -0
  49. data/test/format_test.rb +105 -0
  50. data/test/info_test.rb +60 -0
  51. data/test/reader_test.rb +222 -0
  52. data/test/wavefile_io_test_helper.rb +47 -0
  53. data/test/writer_test.rb +118 -0
  54. metadata +72 -33
  55. data/test/wavefile_test.rb +0 -339
@@ -0,0 +1,128 @@
1
+ module WaveFile
2
+ # Provides the ability to write data to a wave file.
3
+ class Writer
4
+
5
+ # Padding value written to the end of chunks whose payload is an odd number of bytes. The RIFF
6
+ # specification requires that each chunk be aligned to an even number of bytes, even if the byte
7
+ # count is an odd number.
8
+ #
9
+ # See http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf, page 11.
10
+ EMPTY_BYTE = "\000"
11
+
12
+ # The number of bytes at the beginning of a wave file before the sample data in the data chunk
13
+ # starts, assuming this canonical format:
14
+ #
15
+ # RIFF Chunk Header (12 bytes)
16
+ # Format Chunk (No Extension) (16 bytes)
17
+ # Data Chunk Header (8 bytes)
18
+ #
19
+ # All wave files written by Writer use this canonical format.
20
+ CANONICAL_HEADER_BYTE_LENGTH = 36
21
+
22
+
23
+ # Returns a constructed Writer object which is available for writing sample data to the specified
24
+ # file (via the write() method). When all sample data has been written, the Writer should be closed.
25
+ # Note that the wave file being written to will NOT be valid (and playable in other programs) until
26
+ # the Writer has been closed.
27
+ #
28
+ # If a block is given to this method, sample data can be written inside the given block. When the
29
+ # block terminates, the Writer will be automatically closed (and no more sample data can be written).
30
+ #
31
+ # If no block is given, then sample data can be written until the close() method is called.
32
+ def initialize(file_name, format)
33
+ @file = File.open(file_name, "wb")
34
+ @format = format
35
+
36
+ @samples_written = 0
37
+ @pack_code = PACK_CODES[format.bits_per_sample]
38
+
39
+ # Note that the correct sizes for the RIFF and data chunks can't be determined
40
+ # until all samples have been written, so this header as written will be incorrect.
41
+ # When close() is called, the correct sizes will be re-written.
42
+ write_header(0)
43
+
44
+ if block_given?
45
+ yield(self)
46
+ close()
47
+ end
48
+ end
49
+
50
+
51
+ # Appends the sample data in the given Buffer to the end of the wave file.
52
+ #
53
+ # Returns the number of sample that have been written to the file so far.
54
+ # Raises IOError if the Writer has been closed.
55
+ def write(buffer)
56
+ samples = buffer.convert(@format).samples
57
+
58
+ @file.syswrite(samples.flatten.pack(@pack_code))
59
+ @samples_written += samples.length
60
+ end
61
+
62
+
63
+ # Returns true if the Writer is closed, and false if it is open and available for writing.
64
+ def closed?()
65
+ return @file.closed?
66
+ end
67
+
68
+
69
+ # Closes the Writer. After a Writer is closed, no more sample data can be written to it.
70
+ #
71
+ # Note that the wave file will NOT be valid until this method is called. The wave file
72
+ # format requires certain information about the amount of sample data, and this can't be
73
+ # determined until all samples have been written.
74
+ #
75
+ # Returns nothing.
76
+ # Raises IOError if the Writer is already closed.
77
+ def close()
78
+ # The RIFF specification requires that each chunk be aligned to an even number of bytes,
79
+ # even if the byte count is an odd number. Therefore if an odd number of bytes has been
80
+ # written, write an empty padding byte.
81
+ #
82
+ # See http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf, page 11.
83
+ bytes_written = @samples_written * @format.block_align
84
+ if bytes_written.odd?
85
+ @file.syswrite(EMPTY_BYTE)
86
+ end
87
+
88
+ # We can't know what chunk sizes to write for the RIFF and data chunks until all
89
+ # samples have been written, so go back to the beginning of the file and re-write
90
+ # those chunk headers with the correct sizes.
91
+ @file.sysseek(0)
92
+ write_header(@samples_written)
93
+
94
+ @file.close()
95
+ end
96
+
97
+ attr_reader :file_name, :format
98
+
99
+ private
100
+
101
+ # Writes the RIFF chunk header, format chunk, and the header for the data chunk. After this
102
+ # method is called the file will be "queued up" and ready for writing actual sample data.
103
+ def write_header(sample_count)
104
+ sample_data_byte_count = sample_count * @format.block_align
105
+
106
+ # Write the header for the RIFF chunk
107
+ header = CHUNK_IDS[:riff]
108
+ header += [CANONICAL_HEADER_BYTE_LENGTH + sample_data_byte_count].pack(UNSIGNED_INT_32)
109
+ header += WAVEFILE_FORMAT_CODE
110
+
111
+ # Write the format chunk
112
+ header += CHUNK_IDS[:format]
113
+ header += [FORMAT_CHUNK_BYTE_LENGTH].pack(UNSIGNED_INT_32)
114
+ header += [PCM].pack(UNSIGNED_INT_16)
115
+ header += [@format.channels].pack(UNSIGNED_INT_16)
116
+ header += [@format.sample_rate].pack(UNSIGNED_INT_32)
117
+ header += [@format.byte_rate].pack(UNSIGNED_INT_32)
118
+ header += [@format.block_align].pack(UNSIGNED_INT_16)
119
+ header += [@format.bits_per_sample].pack(UNSIGNED_INT_16)
120
+
121
+ # Write the header for the data chunk
122
+ header += CHUNK_IDS[:data]
123
+ header += [sample_data_byte_count].pack(UNSIGNED_INT_32)
124
+
125
+ @file.syswrite(header)
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,121 @@
1
+ require 'test/unit'
2
+ require 'wavefile.rb'
3
+
4
+ include WaveFile
5
+
6
+ class BufferTest < Test::Unit::TestCase
7
+ def test_convert
8
+ old_format = Format.new(1, 16, 44100)
9
+ new_format = Format.new(2, 16, 22050)
10
+
11
+ old_buffer = Buffer.new([-100, 0, 200], old_format)
12
+ new_buffer = old_buffer.convert(new_format)
13
+
14
+ assert_equal([-100, 0, 200], old_buffer.samples)
15
+ assert_equal(1, old_buffer.channels)
16
+ assert_equal(16, old_buffer.bits_per_sample)
17
+ assert_equal(44100, old_buffer.sample_rate)
18
+
19
+ assert_equal([[-100, -100], [0, 0], [200, 200]], new_buffer.samples)
20
+ assert_equal(2, new_buffer.channels)
21
+ assert_equal(16, new_buffer.bits_per_sample)
22
+ assert_equal(22050, new_buffer.sample_rate)
23
+ end
24
+
25
+ def test_convert!
26
+ old_format = Format.new(1, 16, 44100)
27
+ new_format = Format.new(2, 16, 22050)
28
+
29
+ old_buffer = Buffer.new([-100, 0, 200], old_format)
30
+ new_buffer = old_buffer.convert!(new_format)
31
+
32
+ assert(old_buffer.equal?(new_buffer))
33
+ assert_equal([[-100, -100], [0, 0], [200, 200]], old_buffer.samples)
34
+ assert_equal(2, old_buffer.channels)
35
+ assert_equal(16, old_buffer.bits_per_sample)
36
+ assert_equal(22050, old_buffer.sample_rate)
37
+ end
38
+
39
+
40
+ def test_convert_buffer_channels
41
+ Format::SUPPORTED_BITS_PER_SAMPLE.each do |bits_per_sample|
42
+ [44100, 22050].each do |sample_rate|
43
+ # Assert that not changing the number of channels is a no-op
44
+ b = Buffer.new([-100, 0, 200], Format.new(1, bits_per_sample, sample_rate))
45
+ b.convert!(Format.new(1, bits_per_sample, sample_rate))
46
+ assert_equal([-100, 0, 200], b.samples)
47
+
48
+ # Mono => Stereo
49
+ b = Buffer.new([-100, 0, 200], Format.new(1, bits_per_sample, sample_rate))
50
+ b.convert!(Format.new(2, bits_per_sample, sample_rate))
51
+ assert_equal([[-100, -100], [0, 0], [200, 200]], b.samples)
52
+
53
+ # Mono => 3-channel
54
+ b = Buffer.new([-100, 0, 200], Format.new(1, bits_per_sample, sample_rate))
55
+ b.convert!(Format.new(3, bits_per_sample, sample_rate))
56
+ assert_equal([[-100, -100, -100], [0, 0, 0], [200, 200, 200]], b.samples)
57
+
58
+ # Stereo => Mono
59
+ b = Buffer.new([[-100, -100], [0, 0], [200, 50], [8, 1]], Format.new(2, bits_per_sample, sample_rate))
60
+ b.convert!(Format.new(1, bits_per_sample, sample_rate))
61
+ assert_equal([-100, 0, 125, 4], b.samples)
62
+
63
+ # 3-channel => Mono
64
+ b = Buffer.new([[-100, -100, -100], [0, 0, 0], [200, 50, 650], [5, 1, 1], [5, 1, 2]],
65
+ Format.new(3, bits_per_sample, sample_rate))
66
+ b.convert!(Format.new(1, bits_per_sample, sample_rate))
67
+ assert_equal([-100, 0, 300, 2, 2], b.samples)
68
+
69
+ # 3-channel => Stereo
70
+ b = Buffer.new([[-100, -100, -100], [1, 2, 3], [200, 50, 650]],
71
+ Format.new(3, bits_per_sample, sample_rate))
72
+ b.convert!(Format.new(2, bits_per_sample, sample_rate))
73
+ assert_equal([[-100, -100], [1, 2], [200, 50]], b.samples)
74
+
75
+ # Unsupported conversion (4-channel => 3-channel)
76
+ b = Buffer.new([[-100, 200, -300, 400], [1, 2, 3, 4]],
77
+ Format.new(4, bits_per_sample, sample_rate))
78
+ assert_raise(BufferConversionError) { b.convert!(Format.new(3, bits_per_sample, sample_rate)) }
79
+ end
80
+ end
81
+ end
82
+
83
+ def test_convert_buffer_bits_per_sample
84
+ # Assert that not changing the number of channels is a no-op
85
+ b = Buffer.new([-32768, -24576, -16384, -8192, 0, 8192, 16384, 24575, 32767], Format.new(1, 16, 44100))
86
+ b.convert!(Format.new(1, 16, 44100))
87
+ assert_equal([-32768, -24576, -16384, -8192, 0, 8192, 16384, 24575, 32767], b.samples)
88
+
89
+ # 8 => 16, Mono
90
+ b = Buffer.new([0, 32, 64, 96, 128, 160, 192, 223, 255], Format.new(1, 8, 44100))
91
+ b.convert!(Format.new(1, 16, 44100))
92
+ assert_equal([-32768, -24576, -16384, -8192, 0, 8192, 16384, 24320, 32512], b.samples)
93
+
94
+ # 8 => 32, Mono
95
+ b = Buffer.new([0, 32, 64, 96, 128, 160, 192, 223, 255], Format.new(1, 8, 44100))
96
+ b.convert!(Format.new(1, 32, 44100))
97
+ assert_equal([-2147483648, -1610612736, -1073741824, -536870912, 0, 536870912, 1073741824, 1593835520, 2130706432], b.samples)
98
+
99
+ # 16 => 8, Mono
100
+ b = Buffer.new([-32768, -24576, -16384, -8192, 0, 8192, 16384, 24575, 32767], Format.new(1, 16, 44100))
101
+ b.convert!(Format.new(1, 8, 44100))
102
+ assert_equal([0, 32, 64, 96, 128, 160, 192, 223, 255], b.samples)
103
+
104
+ # 16 => 32, Mono
105
+ b = Buffer.new([-32768, -24576, -16384, -8192, 0, 8192, 16384, 24575, 32767], Format.new(1, 16, 44100))
106
+ b.convert!(Format.new(1, 32, 44100))
107
+ assert_equal([-2147483648, -1610612736, -1073741824, -536870912, 0, 536870912, 1073741824, 1610547200, 2147418112], b.samples)
108
+
109
+ # 32 => 8, Mono
110
+ b = Buffer.new([-2147483648, -1610612736, -1073741824, -536870912, 0, 536870912, 1073741824, 1610612735, 2147483647],
111
+ Format.new(1, 32, 44100))
112
+ b.convert!(Format.new(1, 8, 44100))
113
+ assert_equal([0, 32, 64, 96, 128, 160, 192, 223, 255], b.samples)
114
+
115
+ # 32 => 16, Mono
116
+ b = Buffer.new([-2147483648, -1610612736, -1073741824, -536870912, 0, 536870912, 1073741824, 1610612735, 2147483647],
117
+ Format.new(1, 32, 44100))
118
+ b.convert!(Format.new(1, 16, 44100))
119
+ assert_equal([-32768, -24576, -16384, -8192, 0, 8192, 16384, 24575, 32767], b.samples)
120
+ end
121
+ end
@@ -0,0 +1,10 @@
1
+ The files in this folder are not valid wave files, and should not be readable by the WaveFile gem and other audio tools/programs.
2
+
3
+ * **empty.wav** - An empty file, size of 0 bytes.
4
+ * **incomplete_riff_header.wav** - The file consists of the characters "RIFF" and nothing else.
5
+ * **bad_riff_header.wav** - The first four bytes in the file are not "RIFF", which is a magic number required for wave files.
6
+ * **bad_wavefile_format.wav** - The format code inside the RIFF header is not "WAVE".
7
+ * **no_format_chunk.wav** - The file contains a valid RIFF header, but not after that.
8
+ * **empty_format_chunk.wav** - The format chunk has no data in it, despite the format chunk header indicating is is 16 bytes long.
9
+ * **insufficient_format_chunk.wav** - The format chunk has some data, but not the minimum amount required.
10
+ * **no_data_chunk.wav** - The RIFF header and format chunk are valid, but there is no data chunk.
@@ -0,0 +1 @@
1
+ This is not a wave file
File without changes
@@ -0,0 +1,6 @@
1
+ The files in this folder are wave files that do not violate the wave file spec, but can not be read by the WaveFile gem.
2
+
3
+ * **unsupported_audio_format.wav** - The audio format defined in the format chunk is 2, or ADPCM. While a valid format, this gem only supports 1 (PCM).
4
+ * **unsupported_bits_per_sample.wav** - The bits per sample is 24. While this is a valid sample rate, this gem only supports 8, 16, and 32 bits per sample.
5
+ * **bad_channel_count.wav** - The channel count defined in the format chunk is 0.
6
+ * **bad_sample_rate.wav** - The sample rate defined in the format chunk is 0.
@@ -0,0 +1,3 @@
1
+ The files in this folder are valid wave files that the WaveFile gem should be able to read.
2
+
3
+ Note that these files won't necessarily sound like anything if you play them. They intentionally have a small amount of sample data to make tests easy to write and test failures easier to diagnose.
@@ -0,0 +1,105 @@
1
+ require 'test/unit'
2
+ require 'wavefile.rb'
3
+
4
+ include WaveFile
5
+
6
+ class FormatTest < Test::Unit::TestCase
7
+ def test_valid_channels()
8
+ [1, 2, 3, 4, 65535].each do |valid_channels|
9
+ assert_equal(valid_channels, Format.new(valid_channels, 16, 44100).channels)
10
+ end
11
+
12
+ assert_equal(1, Format.new(:mono, 16, 44100).channels)
13
+ assert_equal(2, Format.new(:stereo, 16, 44100).channels)
14
+ end
15
+
16
+ def test_invalid_channels()
17
+ ["dsfsfsdf", :foo, 0, -1, 65536].each do |invalid_channels|
18
+ assert_raise(InvalidFormatError) { Format.new(invalid_channels, 16, 44100) }
19
+ end
20
+ end
21
+
22
+ def test_valid_bits_per_sample()
23
+ assert_equal(8, Format.new(1, 8, 44100).bits_per_sample)
24
+ assert_equal(16, Format.new(1, 16, 44100).bits_per_sample)
25
+ assert_equal(32, Format.new(1, 32, 44100).bits_per_sample)
26
+ end
27
+
28
+ def test_invalid_bits_per_sample()
29
+ ["dsfsfsdf", :foo, 0, 12].each do |invalid_bits_per_sample|
30
+ assert_raise(InvalidFormatError) { Format.new(1, invalid_bits_per_sample, 44100) }
31
+ end
32
+ end
33
+
34
+ def test_valid_sample_rate()
35
+ [1, 44100, 4294967296].each do |valid_sample_rate|
36
+ assert_equal(valid_sample_rate, Format.new(1, 16, valid_sample_rate).sample_rate)
37
+ end
38
+ end
39
+
40
+ def test_invalid_sample_rate()
41
+ ["dsfsfsdf", :foo, 0, -1, 4294967297].each do |invalid_sample_rate|
42
+ assert_raise(InvalidFormatError) { Format.new(1, 16, invalid_sample_rate) }
43
+ end
44
+ end
45
+
46
+ def test_byte_rate()
47
+ format = Format.new(1, 8, 44100)
48
+ assert_equal(44100, format.byte_rate)
49
+
50
+ format = Format.new(1, 16, 44100)
51
+ assert_equal(88200, format.byte_rate)
52
+
53
+ format = Format.new(1, 32, 44100)
54
+ assert_equal(176400, format.byte_rate)
55
+
56
+ format = Format.new(2, 8, 44100)
57
+ assert_equal(88200, format.byte_rate)
58
+
59
+ format = Format.new(2, 16, 44100)
60
+ assert_equal(176400, format.byte_rate)
61
+
62
+ format = Format.new(2, 32, 44100)
63
+ assert_equal(352800, format.byte_rate)
64
+ end
65
+
66
+ def test_block_align()
67
+ [1, :mono].each do |one_channel|
68
+ format = Format.new(one_channel, 8, 44100)
69
+ assert_equal(1, format.block_align)
70
+
71
+ format = Format.new(one_channel, 16, 44100)
72
+ assert_equal(2, format.block_align)
73
+
74
+ format = Format.new(one_channel, 32, 44100)
75
+ assert_equal(4, format.block_align)
76
+ end
77
+
78
+ [2, :stereo].each do |two_channels|
79
+ format = Format.new(two_channels, 8, 44100)
80
+ assert_equal(2, format.block_align)
81
+
82
+ format = Format.new(two_channels, 16, 44100)
83
+ assert_equal(4, format.block_align)
84
+
85
+ format = Format.new(two_channels, 32, 44100)
86
+ assert_equal(8, format.block_align)
87
+ end
88
+ end
89
+
90
+ def test_mono?()
91
+ [1, :mono].each do |one_channel|
92
+ format = Format.new(one_channel, 8, 44100)
93
+ assert_equal(true, format.mono?)
94
+ assert_equal(false, format.stereo?)
95
+ end
96
+ end
97
+
98
+ def test_stereo?()
99
+ [2, :stereo].each do |two_channels|
100
+ format = Format.new(two_channels, 8, 44100)
101
+ assert_equal(false, format.mono?)
102
+ assert_equal(true, format.stereo?)
103
+ end
104
+ end
105
+ end