wavefile 1.1.1 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.markdown +39 -18
  4. data/lib/wavefile/buffer.rb +9 -7
  5. data/lib/wavefile/chunk_readers/format_chunk_reader.rb +11 -7
  6. data/lib/wavefile/duration.rb +3 -3
  7. data/lib/wavefile/reader.rb +13 -4
  8. data/lib/wavefile/sampler_info.rb +1 -1
  9. data/lib/wavefile/sampler_loop.rb +1 -1
  10. data/lib/wavefile/smpte_timecode.rb +1 -1
  11. data/lib/wavefile/writer.rb +3 -3
  12. data/lib/wavefile.rb +17 -17
  13. data/test/fixtures/wave/invalid/bad_wavefile_format.wav +0 -0
  14. data/test/fixtures/wave/invalid/empty_format_chunk.wav +0 -0
  15. data/test/fixtures/wave/invalid/extensible_format_chunk_extension_incomplete.wav +0 -0
  16. data/test/fixtures/wave/invalid/extensible_format_chunk_extension_incomplete_in_incorrectly_sized_chunk.wav +0 -0
  17. data/test/fixtures/wave/invalid/extensible_format_chunk_extension_incomplete_in_large_enough_chunk.wav +0 -0
  18. data/test/fixtures/wave/invalid/extensible_format_chunk_extension_missing.wav +0 -0
  19. data/test/fixtures/wave/invalid/extensible_format_chunk_extension_size_incomplete.wav +0 -0
  20. data/test/fixtures/wave/invalid/extensible_format_chunk_extension_size_incomplete_with_padding_byte.wav +0 -0
  21. data/test/fixtures/wave/invalid/extensible_format_chunk_extension_truncated.wav +0 -0
  22. data/test/fixtures/wave/invalid/extensible_format_chunk_oversized_extension_too_large.wav +0 -0
  23. data/test/fixtures/wave/invalid/float_format_chunk_extension_size_incomplete.wav +0 -0
  24. data/test/fixtures/wave/invalid/float_format_chunk_extension_size_incomplete_with_padding_byte.wav +0 -0
  25. data/test/fixtures/wave/invalid/float_format_chunk_oversized_extension_too_large.wav +0 -0
  26. data/test/fixtures/wave/invalid/format_chunk_extra_bytes_with_odd_size_and_missing_padding_byte.wav +0 -0
  27. data/test/fixtures/wave/invalid/format_chunk_with_extra_byte_and_missing_padding_byte.wav +0 -0
  28. data/test/fixtures/wave/invalid/insufficient_format_chunk.wav +0 -0
  29. data/test/fixtures/wave/invalid/no_data_chunk.wav +0 -0
  30. data/test/fixtures/wave/invalid/no_format_chunk.wav +0 -0
  31. data/test/fixtures/wave/invalid/no_format_chunk_size.wav +0 -0
  32. data/test/fixtures/wave/invalid/no_riff_format.wav +0 -0
  33. data/test/fixtures/wave/invalid/unsupported_format_extension_size_incomplete.wav +0 -0
  34. data/test/fixtures/wave/invalid/unsupported_format_extension_size_incomplete_with_padding_byte.wav +0 -0
  35. data/test/fixtures/wave/invalid/unsupported_format_extension_truncated.wav +0 -0
  36. data/test/fixtures/wave/invalid/unsupported_format_oversized_extension_too_large.wav +0 -0
  37. data/test/fixtures/wave/unsupported/bad_sample_rate.wav +0 -0
  38. data/test/fixtures/wave/unsupported/extensible_container_size_bigger_than_sample_size.wav +0 -0
  39. data/test/fixtures/wave/unsupported/extensible_unsupported_subformat_guid.wav +0 -0
  40. data/test/fixtures/wave/unsupported/unsupported_audio_format.wav +0 -0
  41. data/test/fixtures/wave/unsupported/unsupported_format_code_missing_extension_size.wav +0 -0
  42. data/test/fixtures/wave/unsupported/unsupported_format_code_with_extension.wav +0 -0
  43. data/test/fixtures/wave/unsupported/unsupported_format_code_with_extension_and_extra_bytes.wav +0 -0
  44. data/test/fixtures/wave/unsupported/unsupported_format_code_with_incomplete_extension.wav +0 -0
  45. data/test/fixtures/wave/unsupported/unsupported_format_code_with_oversized_extension.wav +0 -0
  46. data/test/fixtures/wave/unsupported/unsupported_format_code_with_oversized_extension_and_extra_bytes.wav +0 -0
  47. data/test/fixtures/wave/valid/valid_extensible_format_chunk_oversized_extension.wav +0 -0
  48. data/test/fixtures/wave/valid/valid_extensible_format_chunk_oversized_extension_and_extra_bytes.wav +0 -0
  49. data/test/fixtures/wave/valid/valid_extensible_format_chunk_with_extra_bytes.wav +0 -0
  50. data/test/fixtures/wave/valid/valid_float_format_chunk_missing_extension_size.wav +0 -0
  51. data/test/fixtures/wave/valid/valid_float_format_chunk_oversized_extension.wav +0 -0
  52. data/test/fixtures/wave/valid/valid_float_format_chunk_oversized_extension_and_extra_bytes.wav +0 -0
  53. data/test/fixtures/wave/valid/valid_float_format_chunk_with_extra_bytes.wav +0 -0
  54. data/test/fixtures/wave/valid/valid_format_chunk_extra_bytes_with_odd_size_and_padding_byte.wav +0 -0
  55. data/test/fixtures/wave/valid/valid_format_chunk_with_extra_byte_and_padding_byte.wav +0 -0
  56. data/test/fixtures/wave/valid/valid_format_chunk_with_extra_bytes.wav +0 -0
  57. data/test/reader_test.rb +442 -6
  58. data/test/wavefile_io_test_helper.rb +3 -3
  59. data/test/writer_test.rb +1 -1
  60. metadata +40 -8
  61. data/test/fixtures/wave/unsupported/bad_audio_format.wav +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 74dc03a8b43dd34032083def685191fb8795bcc19f0d6dc2459377249e72b262
4
- data.tar.gz: 9388445c1d4f394ea3929b7bb49e01b4db1c3058ec497d17a7e783f94389d093
3
+ metadata.gz: 00fb7f8e8dc794a4cb669d3709cc05a1776f752a27d501668760bf14bae70746
4
+ data.tar.gz: 0a86b94b3c8891b365be3c9ffc17f824b5a97b31d22a9d9a884caf66657a935e
5
5
  SHA512:
6
- metadata.gz: '087d89fb1b379afb39afa0173e41ee67a6c609b4969054bfe3b81eff367410dc46623a4e5e5bf52de1f59b13186f40a935c2eef70168a4b2a75cce5a9fd5487f'
7
- data.tar.gz: cd180b60e9f70e2ec60ff8431192d9aec10aaf06554591a725a9594d5ed8efd1479abb2219495addbe8dd91c4f6ee5220c10371be95dc1836c5f863ba27b7227
6
+ metadata.gz: 7c0978db16d2ea9baca96c9880b8816bd9829bbb7e2c34404d815e58219eb7dc715b0e9e30d6b4b75f6fb2dc1b22b517ececd0fcfd3ac43d76ff148761ed2bf7
7
+ data.tar.gz: a57b221b66a60962d3ad7e3c90ff66b0bee00e848f7aafd2eebc282ef0fefed7998f5a4c2b6297ade454566ff0fbeacf0e7887d892c6e22939e4490edaec80cc
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009-19 Joel Strait
1
+ Copyright (c) 2009-22 Joel Strait
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person
4
4
  obtaining a copy of this software and associated documentation
data/README.markdown CHANGED
@@ -2,14 +2,14 @@ A Ruby gem for reading and writing sound files in Wave format (*.wav).
2
2
 
3
3
  You can use this gem to create Ruby programs that work with audio, such as a [command-line drum machine](https://beatsdrummachine.com). Since it is written in pure Ruby (as opposed to wrapping an existing C library), you can use it without having to compile a separate extension.
4
4
 
5
- For more info, check out the website: <http://wavefilegem.com/>
5
+ For more info, check out the website: <https://wavefilegem.com/>
6
6
 
7
7
  # Example Usage
8
8
 
9
- This short example shows how to append three separate Wave files into a single file:
9
+ This example shows how to append three separate Wave files into a single file:
10
10
 
11
11
  ```ruby
12
- require 'wavefile'
12
+ require "wavefile"
13
13
  include WaveFile
14
14
 
15
15
  FILES_TO_APPEND = ["file1.wav", "file2.wav", "file3.wav"]
@@ -23,7 +23,7 @@ Writer.new("append.wav", Format.new(:stereo, :pcm_16, 44100)) do |writer|
23
23
  end
24
24
  ```
25
25
 
26
- More examples can be found at <http://wavefilegem.com/examples>.
26
+ More examples can be found at <https://wavefilegem.com/examples>.
27
27
 
28
28
 
29
29
  # Installation
@@ -34,16 +34,16 @@ First, install the WaveFile gem from rubygems.org:
34
34
 
35
35
  ...and include it in your Ruby program:
36
36
 
37
- require 'wavefile'
37
+ require "wavefile"
38
38
 
39
- Note that if you're installing the gem into the default Ruby that comes pre-installed on MacOS (as opposed to a Ruby installed via [RVM](http://rvm.io/) or [rbenv](https://github.com/sstephenson/rbenv/)), you should used `sudo gem install wavefile`. Otherwise you might run into a file permission error.
39
+ Note that if you're installing the gem into the default Ruby that comes pre-installed on MacOS (as opposed to a Ruby installed via [RVM](https://rvm.io/) or [rbenv](https://github.com/sstephenson/rbenv/)), you should used `sudo gem install wavefile`. Otherwise you might run into a file permission error.
40
40
 
41
41
 
42
42
  # Compatibility
43
43
 
44
44
  WaveFile has been tested with these Ruby versions, and appears to be compatible with them:
45
45
 
46
- * MRI 2.7.0, 2.6.5, 2.5.7, 2.4.9, 2.3.8, 2.2.10, 2.1.10, 2.0.0-p648
46
+ * MRI 3.2.0, 3.1.3, 3.0.5, 2.7.7, 2.6.10, 2.5.9, 2.4.10, 2.3.8, 2.2.10, 2.1.10, 2.0.0-p648
47
47
 
48
48
  2.0 is the minimum supported Ruby version.
49
49
 
@@ -65,25 +65,46 @@ This gem lets you read and write audio data! You can use it to create Ruby progr
65
65
  * Seamlessly convert between sample formats. Read sample data from a file into any format supported by this gem, regardless of how the sample data is stored in the actual file. Or, create sample data in one format (such as floats between -1.0 and 1.0), but write it to a file in a different format (such as 16-bit PCM).
66
66
  * Automatic file management, similar to how `IO.open` works. That is, you can open a file for reading or writing, and if a block is given, the file will automatically be closed when the block exits.
67
67
  * Query metadata about Wave files (sample rate, number of channels, number of sample frames, etc.), including files that are in a format this gem can't read or write.
68
- * Written in pure Ruby, so it's easy to include in your program. There's no need to compile a separate extension in order to use it.
68
+ * Easy to install, since it's written in pure Ruby. There's no need to compile a separate extension in order to use it.
69
69
 
70
70
 
71
- # Current Release: v1.1.1
71
+ # Current Release: v1.1.2
72
72
 
73
- Released on December 29, 2019, this version contains this change:
73
+ Released on December 30, 2022, this version fixes several edge case bugs related to reading a *.wav file's `"fmt "` chunk. In particular, reading a `"fmt "` chunk that has extra trailing bytes; reading a `"fmt "` chunk in WAVE_FORMAT_EXTENSIBLE format whose chunk extension is missing, incomplete, or has extra trailing bytes; and reading a `"fmt "` chunk whose chunk extension is too large to fit in the chunk. In short, some valid files that were previously rejected can now be read, and some invalid files are handled more properly.
74
74
 
75
- * Removes `warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call` output when reading a file with a `smpl` chunk using Ruby 2.7.0. (And presumably, higher Ruby versions as well, but Ruby 2.7.0 is the most recent Ruby version at the time of this release).
75
+ The full details:
76
+
77
+ * **Bug Fix:** Files that have extra bytes at the end of the `"fmt "` chunk can now be read.
78
+
79
+ If the format code is `1`, the `"fmt "` chunk has extra bytes if the chunk body size is greater than 16 bytes. Otherwise, "extra bytes" means the chunk contains bytes after the chunk extension (not including the required padding byte for an odd-sized chunk).
80
+
81
+ Previously, attempting to open certain files like this via `Reader.new` would result in `InvalidFormatError` being raised with a misleading `"Not a supported wave file. The format chunk extension is shorter than expected."` message. This was misleading because if the format code is `1`, the `"fmt "` chunk won't actually have a chunk extension, and for other format codes the chunk extension might actually be the expected size or larger. When reading a file like this, any extra data in the `"fmt "` chunk beyond what is expected based on the relevant format code will now be ignored.
82
+
83
+ * There was a special case where a file like this _could_ be opened correctly. If the format code was `1`, and the value of bytes 16 and 17 (0-based), when interpreted as a 16-bit unsigned little-endian integer, happened to be the same as the number of subsequent bytes in the chunk, the file could be opened without issue. For example, if the `"fmt "` chunk size was `22`, the format code was `1`, and the value of bytes 16 and 17 was `4` (when interpreted as a 16-bit unsigned little-endian integer), the file could be opened correctly.
84
+ * There was another special case where `InvalidFormatError` would be incorrectly raised, but the error message would be different (and also misleading). If the format code was `1`, and there was exactly 1 extra byte in the `"fmt "` chunk (i.e. the chunk size was 17 bytes), the error message would be `"Not a supported wave file. The format chunk is missing an expected extension."` This was misleading because when the format code is `1`, the `"fmt "` chunk doesn't have a chunk extension.
85
+ * Thanks to [@CromonMS](https://github.com/CromonMS) for reporting this as an issue.
86
+
87
+ * **Bug Fix:** Files in WAVE_FORMAT_EXTENSIBLE format with a missing or incomplete `"fmt "` chunk extension can no longer be opened using `Reader.new`.
88
+
89
+ Previously, a `Reader` instance could be constructed for a file like this, but the relevant fields on the object returned by `Reader#native_format` would contain `nil` or `""` values for these fields, and no sample data could be read from the file. Since files like this are missing required fields that don't necessarily have sensible default values, it seems like it shouldn't be possible to create a `Reader` instance from them. After this fix, attempting to do so will cause `InvalidFormatError` to be raised.
76
90
 
91
+ * **Bug Fix:** Files in WAVE_FORMAT_EXTENSIBLE format that have extra bytes at the end of the `"fmt "` chunk extension can now be read.
77
92
 
78
- # Previous Release: v1.1.0
93
+ This is similar but different from the first bug above; that bug refers to extra bytes _after_ the chunk extension, while this bug refers to extra bytes _inside_ the chunk extension. A WAVE_FORMAT_EXTENSIBLE `"fmt "` chunk extension has extra bytes if it is larger than 22 bytes.
79
94
 
80
- Released on January 20, 2019, this version has these changes:
95
+ Previously, a `Reader` instance could be constructed for a file like this, but `Reader#native_format#sub_audio_format_guid` would have an incorrect value, and sample data could not be read from the file. After this fix, this field will have the correct value, and if it is one of the supported values then sample data can be read. Any extra data at the end of the chunk extension will be ignored.
81
96
 
82
- * **Can read `smpl` chunk data from files that contain this kind of chunk.** If a *.wav file contains a `smpl` chunk, then `Reader.sampler_info` will return a `SamplerInfo` instance with the relevant data (or `nil` otherwise). Thanks to [@henrikj242](https://github.com/henrikj242) for suggesting this feature and providing the base implementation.
83
- * **More informative errors raised by `Reader.new`**. When attempting to read an invalid file, the error message now provides more detail about why the file is invalid.
84
- * **Bug Fix**: The master RIFF chunk size for files written by the gem will now take into account padding bytes written for child chunks. For example, when writing a file with a `data` chunk whose body has an odd number of bytes, the master RIFF chunk's size will be 1 byte larger (to take the empty padding byte at the end of the `data` chunk into account).
85
- * **Bug Fix**: If the stated `data` chunk size is larger than the actual number of bytes in the file, `Reader.current_sample_frame` will be correct when attempting to read past the end of the chunk. For example, if a `data` chunk says it has 2000 sample frames, but there are only 1000 sample frames remaining in the file, then after calling `Reader.read(1500)`, `Reader.current_sample_frame` will have a value of `1000`, not `1500`. (This bug did not occur for files in which the data chunk listed the correct size).
86
- * **Bug Fix**: Fixed off-by-one error in the maximum allowed value for `Format#sample rate`. The correct maximum sample rate is now 4_294_967_295; previously it allowed a maximum of 4_294_967_296.
97
+ Implicit in this scenario is that the `"fmt "` chunk has a stated size large enough to fit the oversized chunk extension. For cases where it doesn't, see the next bug fix below.
98
+
99
+ * **Bug Fix:** More accurate message on the `InvalidFormatError` raised when reading a file whose `"fmt "` chunk extension is too large to fit in the chunk.
100
+
101
+ The message will now correctly state that the chunk extension is too large, rather than `"Not a supported wave file. The format chunk extension is shorter than expected."`. As an example of what "too large" means, if a `"fmt "` chunk has a size of 50 bytes, then any chunk extension larger than 32 bytes will be too large and overflow out of the chunk, since a chunk extension's content always starts at byte 18 (0-based).
102
+
103
+ # Previous Release: v1.1.1
104
+
105
+ Released on December 29, 2019, this version contains this change:
106
+
107
+ * Removes `warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call` output when reading a file with a `smpl` chunk using Ruby 2.7.0. (And presumably, higher Ruby versions as well, but Ruby 2.7.0 is the most recent Ruby version at the time of this release).
87
108
 
88
109
  For changes in previous versions, visit <https://github.com/jstrait/wavefile/releases>.
89
110
 
@@ -37,15 +37,17 @@ module WaveFile
37
37
  # don't match, unexpected things will happen.
38
38
  #
39
39
  # Examples
40
+ # # One cycle of a floating point 441Hz mono square wave
41
+ # samples = ([0.5] * 50) + ([-0.5] * 50)
42
+ # buffer = Buffer.new(samples, Format.new(:mono, :float, 44100))
40
43
  #
41
- # samples = ([0.5] * 50) + ([-0.5] * 50) # A floating point 440Hz mono square wave
42
- # buffer = Buffer.new(samples, Format.new(:mono, :float, 44100)
44
+ # # One cycle of a floating point 441Hz stereo square wave
45
+ # samples = ([0.5, 0.5] * 50) + ([-0.5, -0.5] * 50)
46
+ # buffer = Buffer.new(samples, Format.new(2, :float, 44100))
43
47
  #
44
- # samples = ([0.5, 0.5] * 50) + ([-0.5, -0.5] * 50) # A 440Hz stereo square wave
45
- # buffer = Buffer.new(samples, Format.new(2, :float, 44100)
46
- #
47
- # samples = ([16000] * 50) + ([-16000] * 50) # A 16-bit PCM 440Hz mono square wave
48
- # buffer = Buffer.new(samples, Format.new(1, :pcm_16, 44100)
48
+ # # One cycle of a 16-bit PCM 441Hz mono square wave
49
+ # samples = ([16000] * 50) + ([-16000] * 50)
50
+ # buffer = Buffer.new(samples, Format.new(1, :pcm_16, 44100))
49
51
  #
50
52
  # Returns a constructed Buffer.
51
53
  def initialize(samples, format)
@@ -22,21 +22,25 @@ module WaveFile
22
22
  format_chunk[:block_align],
23
23
  format_chunk[:bits_per_sample] = raw_bytes.slice!(0...MINIMUM_CHUNK_SIZE).unpack("vvVVvv")
24
24
 
25
- if @chunk_size > MINIMUM_CHUNK_SIZE
26
- format_chunk[:extension_size] = raw_bytes.slice!(0...2).unpack(UNSIGNED_INT_16).first
27
-
28
- if format_chunk[:extension_size] == nil
25
+ if format_chunk[:audio_format] == FORMAT_CODES[:extensible] || (format_chunk[:audio_format] != FORMAT_CODES[:pcm] && @chunk_size > MINIMUM_CHUNK_SIZE)
26
+ if raw_bytes.length < 2
29
27
  raise_error InvalidFormatError, "The format chunk is missing an expected extension."
30
28
  end
31
29
 
32
- if format_chunk[:extension_size] != raw_bytes.length
33
- raise_error InvalidFormatError, "The format chunk extension is shorter than expected."
30
+ format_chunk[:extension_size] = raw_bytes.slice!(0...2).unpack(UNSIGNED_INT_16).first
31
+
32
+ if format_chunk[:extension_size] > raw_bytes.length
33
+ raise_error InvalidFormatError, "The format chunk extension size of #{format_chunk[:extension_size]} bytes is too large to fit in the format chunk. The format chunk has a stated size of #{@chunk_size} bytes, with #{raw_bytes.length} bytes available for the extension."
34
34
  end
35
35
 
36
36
  if format_chunk[:audio_format] == FORMAT_CODES[:extensible]
37
+ if format_chunk[:extension_size] < 22
38
+ raise_error InvalidFormatError, "The format chunk extension size of #{format_chunk[:extension_size]} bytes is too small. Since the format chunk has a format code of #{FORMAT_CODES[:extensible]} (i.e. WAVE_FORMAT_EXTENSIBLE), the extension size must be at least 22 bytes."
39
+ end
40
+
37
41
  format_chunk[:valid_bits_per_sample] = raw_bytes.slice!(0...2).unpack(UNSIGNED_INT_16).first
38
42
  format_chunk[:speaker_mapping] = raw_bytes.slice!(0...4).unpack(UNSIGNED_INT_32).first
39
- format_chunk[:sub_audio_format_guid] = raw_bytes
43
+ format_chunk[:sub_audio_format_guid] = raw_bytes.slice!(0...16)
40
44
  end
41
45
  end
42
46
 
@@ -37,17 +37,17 @@ module WaveFile
37
37
  sample_frames_per_hour = sample_frames_per_minute * 60
38
38
  @hours, @minutes, @seconds, @milliseconds = 0, 0, 0, 0
39
39
 
40
- if(sample_frame_count >= sample_frames_per_hour)
40
+ if sample_frame_count >= sample_frames_per_hour
41
41
  @hours = sample_frame_count / sample_frames_per_hour
42
42
  sample_frame_count -= sample_frames_per_hour * @hours
43
43
  end
44
44
 
45
- if(sample_frame_count >= sample_frames_per_minute)
45
+ if sample_frame_count >= sample_frames_per_minute
46
46
  @minutes = sample_frame_count / sample_frames_per_minute
47
47
  sample_frame_count -= sample_frames_per_minute * @minutes
48
48
  end
49
49
 
50
- if(sample_frame_count >= sample_frames_per_second)
50
+ if sample_frame_count >= sample_frames_per_second
51
51
  @seconds = sample_frame_count / sample_frames_per_second
52
52
  sample_frame_count -= sample_frames_per_second * @seconds
53
53
  end
@@ -56,9 +56,9 @@ module WaveFile
56
56
  end
57
57
 
58
58
 
59
- # Public: Reads sample data of the into successive Buffers of the specified size, until there is
60
- # no more sample data to be read. When all sample data has been read, the Reader is automatically
61
- # closed. Each Buffer is passed to the given block.
59
+ # Public: Starting from the current reading position, reads sample frames into successive Buffers
60
+ # of the specified size, until there are no more sample frames to be read. When the final sample
61
+ # frame has been read the Reader is automatically closed. Each Buffer is passed to the given block.
62
62
  #
63
63
  # If the Reader is constructed from an open IO, the IO is NOT closed after all sample data is
64
64
  # read. However, the Reader will be closed and any attempt to continue to read from it will
@@ -93,6 +93,15 @@ module WaveFile
93
93
  # # Although Reader is closed, file still needs to be manually closed
94
94
  # file.close
95
95
  #
96
+ # reader = Reader.new("my_file.wav")
97
+ # reader.read(100)
98
+ # # Reading using `each_buffer` will start at the 101st sample frame:
99
+ # reader.each_buffer do |buffer|
100
+ # puts "#{buffer.samples.length} sample frames read"
101
+ # end
102
+ # # At this point, the Reader is now closed (even without
103
+ # # a call to `close()`)
104
+ #
96
105
  # Returns nothing. Has side effect of closing the Reader.
97
106
  def each_buffer(sample_frame_count=4096)
98
107
  begin
@@ -116,7 +125,7 @@ module WaveFile
116
125
  #
117
126
  # Raises UnsupportedFormatError if file is in a format that can't be read by this gem.
118
127
  #
119
- # Raises ReaderClosedError if the Writer has been closed.
128
+ # Raises ReaderClosedError if the Reader has been closed.
120
129
  #
121
130
  # Raises EOFError if no samples could be read due to reaching the end of the file.
122
131
  def read(sample_frame_count)
@@ -11,7 +11,7 @@ module WaveFile
11
11
  # Public: Provides a way to indicate the data contained in a "smpl" chunk.
12
12
  # That is, information about how the *.wav file could be used by a
13
13
  # sampler, such as the file's MIDI note or loop points. If a *.wav
14
- # file contains a "smpl" chunk, then Reader.sampler_info will
14
+ # file contains a "smpl" chunk, then Reader#sampler_info will
15
15
  # return an instance of this object with the relevant info.
16
16
  class SamplerInfo
17
17
  VALID_32_BIT_INTEGER_RANGE = 0..4_294_967_295 # :nodoc:
@@ -12,7 +12,7 @@ module WaveFile
12
12
  # Public: Provides a way to indicate the data about sampler loop points
13
13
  # in a file's "smpl" chunk. That is, information about how a sampler
14
14
  # could loop between a sample range while playing this *.wav as a note.
15
- # If a *.wav file contains a "smpl" chunk, then Reader.sampler_info.loops
15
+ # If a *.wav file contains a "smpl" chunk, then Reader#sampler_info#loops
16
16
  # will return an array of SamplerLoop objects with the relevant info.
17
17
  class SamplerLoop
18
18
  VALID_32_BIT_INTEGER_RANGE = 0..4_294_967_295 # :nodoc:
@@ -6,7 +6,7 @@ module WaveFile
6
6
  class InvalidSMPTETimecodeError < StandardError; end
7
7
 
8
8
  # Public: Represents an SMPTE timecode: https://en.wikipedia.org/wiki/SMPTE_timecode
9
- # If a *.wav file has a "smpl" chunk, then Reader.sampler_info.smpte_offset
9
+ # If a *.wav file has a "smpl" chunk, then Reader#sampler_info#smpte_offset
10
10
  # will return an instance of this class.
11
11
  class SMPTETimecode
12
12
  VALID_8_BIT_UNSIGNED_INTEGER_RANGE = 0..255 # :nodoc:
@@ -242,10 +242,10 @@ module WaveFile
242
242
  # Data Chunk Header (8 bytes)
243
243
  #
244
244
  # All wave files written by Writer use this canonical format.
245
- CANONICAL_HEADER_BYTE_LENGTH = {:pcm => 36, :float => 50, :extensible => 72}.freeze # :nodoc:
245
+ CANONICAL_HEADER_BYTE_LENGTH = {pcm: 36, float: 50, extensible: 72}.freeze # :nodoc:
246
246
 
247
247
  # Internal
248
- FORMAT_CHUNK_BYTE_LENGTH = {:pcm => 16, :float => 18, :extensible => 40}.freeze # :nodoc:
248
+ FORMAT_CHUNK_BYTE_LENGTH = {pcm: 16, float: 18, extensible: 40}.freeze # :nodoc:
249
249
 
250
250
  # Internal
251
251
  SPEAKER_MAPPING_BIT_VALUES = {
@@ -273,7 +273,7 @@ module WaveFile
273
273
  # Internal
274
274
  def pack_speaker_mapping(speaker_mapping)
275
275
  speaker_mapping.inject(0) do |result, speaker|
276
- result |= SPEAKER_MAPPING_BIT_VALUES[speaker]
276
+ result | SPEAKER_MAPPING_BIT_VALUES[speaker]
277
277
  end
278
278
  end
279
279
 
data/lib/wavefile.rb CHANGED
@@ -10,28 +10,28 @@ require 'wavefile/unvalidated_format'
10
10
  require 'wavefile/writer'
11
11
 
12
12
  module WaveFile
13
- VERSION = "1.1.1"
13
+ VERSION = "1.1.2"
14
14
 
15
15
  WAVEFILE_FORMAT_CODE = "WAVE" # :nodoc:
16
- FORMAT_CODES = {:pcm => 1, :float => 3, :extensible => 65534}.freeze # :nodoc:
16
+ FORMAT_CODES = {pcm: 1, float: 3, extensible: 65534}.freeze # :nodoc:
17
17
  SUB_FORMAT_GUID_PCM = String.new("\x01\x00\x00\x00\x00\x00\x10\x00\x80\x00\x00\xAA\x00\x38\x9B\x71").force_encoding("ASCII-8BIT").freeze # :nodoc:
18
18
  SUB_FORMAT_GUID_FLOAT = String.new("\x03\x00\x00\x00\x00\x00\x10\x00\x80\x00\x00\xAA\x00\x38\x9B\x71").force_encoding("ASCII-8BIT").freeze # :nodoc:
19
- CHUNK_IDS = {:riff => "RIFF",
20
- :format => "fmt ",
21
- :data => "data",
22
- :fact => "fact",
23
- :silence => "slnt",
24
- :cue => "cue ",
25
- :playlist => "plst",
26
- :list => "list",
27
- :label => "labl",
28
- :labeled_text => "ltxt",
29
- :note => "note",
30
- :sample => "smpl",
31
- :instrument => "inst" }.freeze # :nodoc:
19
+ CHUNK_IDS = {riff: "RIFF",
20
+ format: "fmt ",
21
+ data: "data",
22
+ fact: "fact",
23
+ silence: "slnt",
24
+ cue: "cue ",
25
+ playlist: "plst",
26
+ list: "list",
27
+ label: "labl",
28
+ labeled_text: "ltxt",
29
+ note: "note",
30
+ sample: "smpl",
31
+ instrument: "inst" }.freeze # :nodoc:
32
32
 
33
- PACK_CODES = {:pcm => { 8 => "C*", 16 => "s<*", 24 => "C*", 32 => "l<*"}.freeze,
34
- :float => { 32 => "e*", 64 => "E*"}.freeze}.freeze # :nodoc:
33
+ PACK_CODES = {pcm: { 8 => "C*", 16 => "s<*", 24 => "C*", 32 => "l<*"}.freeze,
34
+ float: { 32 => "e*", 64 => "E*"}.freeze}.freeze # :nodoc:
35
35
 
36
36
  UNSIGNED_INT_16 = "v" # :nodoc:
37
37
  UNSIGNED_INT_32 = "V" # :nodoc:
data/test/reader_test.rb CHANGED
@@ -49,6 +49,73 @@ class ReaderTest < Minitest::Test
49
49
  # The format chunk has some data, but not all of the minimum required.
50
50
  "invalid/insufficient_format_chunk.wav",
51
51
 
52
+ # The format chunk has a size of 17, but is missing the required padding byte
53
+ # The extra byte is part of what would be the chunk extension size if the format
54
+ # code weren't 1.
55
+ "invalid/format_chunk_with_extra_byte_and_missing_padding_byte.wav",
56
+
57
+ # The format chunk has an odd size with extra bytes at the end, but is
58
+ # missing the required padding byte
59
+ "invalid/format_chunk_extra_bytes_with_odd_size_and_missing_padding_byte.wav",
60
+
61
+ # The format chunk is floating point but the extension size field is incomplete
62
+ "invalid/float_format_chunk_extension_size_incomplete.wav",
63
+
64
+ # The format chunk is floating point but the extension size field is incomplete.
65
+ # The padding byte which is present should not be interpreted as part of the size field.
66
+ "invalid/float_format_chunk_extension_size_incomplete_with_padding_byte.wav",
67
+
68
+ # The format chunk is floating point and has an oversized extension,
69
+ # and the extension is too large to fit in the stated size of the chunk
70
+ "invalid/float_format_chunk_oversized_extension_too_large.wav",
71
+
72
+ # The format chunk is extensible, but the required extension is not present
73
+ "invalid/extensible_format_chunk_extension_missing.wav",
74
+
75
+ # The format chunk is extensible, but the extension size field is incomplete.
76
+ # The required padding byte is present.
77
+ "invalid/extensible_format_chunk_extension_size_incomplete_with_padding_byte.wav",
78
+
79
+ # The format chunk is extensible, but the extension size field is incomplete.
80
+ # The required padding byte is not present.
81
+ "invalid/extensible_format_chunk_extension_size_incomplete.wav",
82
+
83
+ # The format chunk is extensible, but the extension is shorter than required
84
+ "invalid/extensible_format_chunk_extension_incomplete.wav",
85
+
86
+ # The format chunk is extensible, but the extension is shorter than required
87
+ # (even though the chunk is large enough to contain a full extension).
88
+ "invalid/extensible_format_chunk_extension_incomplete_in_large_enough_chunk.wav",
89
+
90
+ # The format chunk is extensible, but the extension is shorter than required
91
+ # (the chunk has a stated size that is large enough, although it is larger than the actual amount of data).
92
+ "invalid/extensible_format_chunk_extension_incomplete_in_incorrectly_sized_chunk.wav",
93
+
94
+ # The format chunk is extensible, but the chunk doesn't have enough room for the extension
95
+ "invalid/extensible_format_chunk_extension_truncated.wav",
96
+
97
+ # The format chunk is extensible and has an oversized extension,
98
+ # and the extension is too large to fit in the stated size of the chunk
99
+ "invalid/extensible_format_chunk_oversized_extension_too_large.wav",
100
+
101
+ # The format chunk has an unsupported format code (not an error),
102
+ # but the extension size field is incomplete.
103
+ "invalid/unsupported_format_extension_size_incomplete.wav",
104
+
105
+ # The format chunk has an unsupported format code (not an error),
106
+ # but the extension size field is incomplete.
107
+ # The padding byte which is present should not be interpreted as part of the size field.
108
+ "invalid/unsupported_format_extension_size_incomplete_with_padding_byte.wav",
109
+
110
+ # The format chunk has an unsupported format code (not an error),
111
+ # but the chunk doesn't have enough room for the extension.
112
+ "invalid/unsupported_format_extension_truncated.wav",
113
+
114
+ # The format chunk has an unsupported format code (not an error),
115
+ # and an oversized extension. The extension is too large to fit in
116
+ # the stated size of the chunk.
117
+ "invalid/unsupported_format_oversized_extension_too_large.wav",
118
+
52
119
  # The RIFF header and format chunk are OK, but there is no data chunk
53
120
  "invalid/no_data_chunk.wav",
54
121
 
@@ -127,9 +194,36 @@ class ReaderTest < Minitest::Test
127
194
 
128
195
  def test_read_from_unsupported_format
129
196
  unsupported_fixtures = [
130
- # Audio format is 2, which is not supported
197
+ # Format code has an unsupported value
131
198
  "unsupported/unsupported_audio_format.wav",
132
199
 
200
+ # Format code has an unsupported value, and the format
201
+ # chunk has an extension.
202
+ "unsupported/unsupported_format_code_with_extension.wav",
203
+
204
+ # Format code has an unsupported value, and the format chunk does not
205
+ # have the expected "extension size" field. This field is not required
206
+ # by the gem so this should not cause `InvalidFormatError` to be raised.
207
+ "unsupported/unsupported_format_code_missing_extension_size.wav",
208
+
209
+ # Format code has an unsupported value, the format chunk has an extension,
210
+ # and extra bytes follow the extension.
211
+ "unsupported/unsupported_format_code_with_extension_and_extra_bytes.wav",
212
+
213
+ # Format code has an unsupported value, and a chunk extension that
214
+ # is smaller than is should be for the given format code. However,
215
+ # this should not cause an error because chunk extensions for unsupported
216
+ # formats are not parsed.
217
+ "unsupported/unsupported_format_code_with_incomplete_extension.wav",
218
+
219
+ # Format code has an unsupported value, and the format chunk has an oversized
220
+ # extension with extra bytes at the end.
221
+ "unsupported/unsupported_format_code_with_oversized_extension.wav",
222
+
223
+ # Format code has an unsupported value, the format chunk has an oversized extension
224
+ # with extra bytes at the end, and extra bytes follow the extension.
225
+ "unsupported/unsupported_format_code_with_oversized_extension_and_extra_bytes.wav",
226
+
133
227
  # Bits per sample is 20, which is not supported
134
228
  "unsupported/unsupported_bits_per_sample.wav",
135
229
 
@@ -240,7 +334,9 @@ class ReaderTest < Minitest::Test
240
334
  assert_equal(2, reader.native_format.channels)
241
335
  assert_equal(16, reader.native_format.bits_per_sample)
242
336
  assert_equal(44100, reader.native_format.sample_rate)
337
+ assert_equal(16, reader.native_format.valid_bits_per_sample)
243
338
  assert_equal([:front_left, :front_right], reader.native_format.speaker_mapping)
339
+ assert_equal(WaveFile::SUB_FORMAT_GUID_PCM, reader.native_format.sub_audio_format_guid)
244
340
  assert_equal(2, reader.format.channels)
245
341
  assert_equal(16, reader.format.bits_per_sample)
246
342
  assert_equal(44100, reader.format.sample_rate)
@@ -267,7 +363,9 @@ class ReaderTest < Minitest::Test
267
363
  assert_equal(2, reader.native_format.channels)
268
364
  assert_equal(24, reader.native_format.bits_per_sample)
269
365
  assert_equal(44100, reader.native_format.sample_rate)
366
+ assert_equal(24, reader.native_format.valid_bits_per_sample)
270
367
  assert_equal([:undefined, :undefined], reader.native_format.speaker_mapping)
368
+ assert_equal(WaveFile::SUB_FORMAT_GUID_PCM, reader.native_format.sub_audio_format_guid)
271
369
  assert_equal(2, reader.format.channels)
272
370
  assert_equal(24, reader.format.bits_per_sample)
273
371
  assert_equal(44100, reader.format.sample_rate)
@@ -294,7 +392,9 @@ class ReaderTest < Minitest::Test
294
392
  assert_equal(2, reader.native_format.channels)
295
393
  assert_equal(16, reader.native_format.bits_per_sample)
296
394
  assert_equal(44100, reader.native_format.sample_rate)
395
+ assert_equal(16, reader.native_format.valid_bits_per_sample)
297
396
  assert_equal([:front_left, :front_right, :front_center, :low_frequency], reader.native_format.speaker_mapping)
397
+ assert_equal(WaveFile::SUB_FORMAT_GUID_PCM, reader.native_format.sub_audio_format_guid)
298
398
  assert_equal(2, reader.format.channels)
299
399
  assert_equal(16, reader.format.bits_per_sample)
300
400
  assert_equal(44100, reader.format.sample_rate)
@@ -321,6 +421,7 @@ class ReaderTest < Minitest::Test
321
421
  assert_equal(2, reader.native_format.channels)
322
422
  assert_equal(16, reader.native_format.bits_per_sample)
323
423
  assert_equal(44100, reader.native_format.sample_rate)
424
+ assert_equal(16, reader.native_format.valid_bits_per_sample)
324
425
  # Extra bits for speakers beyond the first 18 are set in the file, but these bits should be ignored
325
426
  assert_equal([:front_left,
326
427
  :front_right,
@@ -340,6 +441,7 @@ class ReaderTest < Minitest::Test
340
441
  :top_back_left,
341
442
  :top_back_center,
342
443
  :top_back_right], reader.native_format.speaker_mapping)
444
+ assert_equal(WaveFile::SUB_FORMAT_GUID_PCM, reader.native_format.sub_audio_format_guid)
343
445
  assert_equal(2, reader.format.channels)
344
446
  assert_equal(16, reader.format.bits_per_sample)
345
447
  assert_equal(44100, reader.format.sample_rate)
@@ -366,7 +468,9 @@ class ReaderTest < Minitest::Test
366
468
  assert_equal(2, reader.native_format.channels)
367
469
  assert_equal(16, reader.native_format.bits_per_sample)
368
470
  assert_equal(44100, reader.native_format.sample_rate)
471
+ assert_equal(16, reader.native_format.valid_bits_per_sample)
369
472
  assert_equal([:undefined, :undefined], reader.native_format.speaker_mapping)
473
+ assert_equal(WaveFile::SUB_FORMAT_GUID_PCM, reader.native_format.sub_audio_format_guid)
370
474
  assert_equal(2, reader.format.channels)
371
475
  assert_equal(16, reader.format.bits_per_sample)
372
476
  assert_equal(44100, reader.format.sample_rate)
@@ -417,6 +521,308 @@ class ReaderTest < Minitest::Test
417
521
  assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_16] * 24, buffers[2].samples)
418
522
  end
419
523
 
524
+ def test_read_float_format_chunk_missing_extension_size
525
+ reader = Reader.new(fixture("valid/valid_float_format_chunk_missing_extension_size.wav"))
526
+
527
+ assert_equal(3, reader.native_format.audio_format)
528
+ assert_equal(1, reader.native_format.channels)
529
+ assert_equal(32, reader.native_format.bits_per_sample)
530
+ assert_equal(44100, reader.native_format.sample_rate)
531
+ assert_nil(reader.native_format.speaker_mapping)
532
+ assert_nil(reader.native_format.sub_audio_format_guid)
533
+ assert_nil(reader.native_format.valid_bits_per_sample)
534
+ assert_equal(1, reader.format.channels)
535
+ assert_equal(32, reader.format.bits_per_sample)
536
+ assert_equal(44100, reader.format.sample_rate)
537
+ assert_equal([:front_center], reader.format.speaker_mapping)
538
+ assert_equal(false, reader.closed?)
539
+ assert_equal(0, reader.current_sample_frame)
540
+ assert_equal(2240, reader.total_sample_frames)
541
+ assert_equal(true, reader.readable_format?)
542
+ assert_nil(reader.sampler_info)
543
+ reader.close
544
+
545
+ buffers = read_file("valid/valid_float_format_chunk_missing_extension_size.wav", 1024)
546
+
547
+ assert_equal(3, buffers.length)
548
+ assert_equal([1024, 1024, 192], buffers.map {|buffer| buffer.samples.length })
549
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:float] * 128, buffers[0].samples)
550
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:float] * 128, buffers[1].samples)
551
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:float] * 24, buffers[2].samples)
552
+ end
553
+
554
+ def test_read_format_chunk_with_extra_bytes
555
+ reader = Reader.new(fixture("valid/valid_format_chunk_with_extra_bytes.wav"))
556
+
557
+ assert_equal(1, reader.native_format.audio_format)
558
+ assert_equal(1, reader.native_format.channels)
559
+ assert_equal(8, reader.native_format.bits_per_sample)
560
+ assert_equal(44100, reader.native_format.sample_rate)
561
+ assert_nil(reader.native_format.speaker_mapping)
562
+ assert_nil(reader.native_format.sub_audio_format_guid)
563
+ assert_nil(reader.native_format.valid_bits_per_sample)
564
+ assert_equal(1, reader.format.channels)
565
+ assert_equal(8, reader.format.bits_per_sample)
566
+ assert_equal(44100, reader.format.sample_rate)
567
+ assert_equal([:front_center], reader.format.speaker_mapping)
568
+ assert_equal(false, reader.closed?)
569
+ assert_equal(0, reader.current_sample_frame)
570
+ assert_equal(2240, reader.total_sample_frames)
571
+ assert_equal(true, reader.readable_format?)
572
+ assert_nil(reader.sampler_info)
573
+ reader.close
574
+
575
+ buffers = read_file("valid/valid_format_chunk_with_extra_bytes.wav", 1024)
576
+
577
+ assert_equal(3, buffers.length)
578
+ assert_equal([1024, 1024, 192], buffers.map {|buffer| buffer.samples.length })
579
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 128, buffers[0].samples)
580
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 128, buffers[1].samples)
581
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 24, buffers[2].samples)
582
+ end
583
+
584
+ def test_read_format_chunk_with_extra_byte_and_padding_byte
585
+ reader = Reader.new(fixture("valid/valid_format_chunk_with_extra_byte_and_padding_byte.wav"))
586
+
587
+ assert_equal(1, reader.native_format.audio_format)
588
+ assert_equal(1, reader.native_format.channels)
589
+ assert_equal(8, reader.native_format.bits_per_sample)
590
+ assert_equal(44100, reader.native_format.sample_rate)
591
+ assert_nil(reader.native_format.speaker_mapping)
592
+ assert_nil(reader.native_format.sub_audio_format_guid)
593
+ assert_nil(reader.native_format.valid_bits_per_sample)
594
+ assert_equal(1, reader.format.channels)
595
+ assert_equal(8, reader.format.bits_per_sample)
596
+ assert_equal(44100, reader.format.sample_rate)
597
+ assert_equal([:front_center], reader.format.speaker_mapping)
598
+ assert_equal(false, reader.closed?)
599
+ assert_equal(0, reader.current_sample_frame)
600
+ assert_equal(2240, reader.total_sample_frames)
601
+ assert_equal(true, reader.readable_format?)
602
+ assert_nil(reader.sampler_info)
603
+ reader.close
604
+
605
+ buffers = read_file("valid/valid_format_chunk_with_extra_byte_and_padding_byte.wav", 1024)
606
+
607
+ assert_equal(3, buffers.length)
608
+ assert_equal([1024, 1024, 192], buffers.map {|buffer| buffer.samples.length })
609
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 128, buffers[0].samples)
610
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 128, buffers[1].samples)
611
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 24, buffers[2].samples)
612
+ end
613
+
614
+ def test_read_format_chunk_with_extra_bytes_with_odd_size_and_padding_byte
615
+ reader = Reader.new(fixture("valid/valid_format_chunk_extra_bytes_with_odd_size_and_padding_byte.wav"))
616
+
617
+ assert_equal(1, reader.native_format.audio_format)
618
+ assert_equal(1, reader.native_format.channels)
619
+ assert_equal(8, reader.native_format.bits_per_sample)
620
+ assert_equal(44100, reader.native_format.sample_rate)
621
+ assert_nil(reader.native_format.speaker_mapping)
622
+ assert_nil(reader.native_format.sub_audio_format_guid)
623
+ assert_nil(reader.native_format.valid_bits_per_sample)
624
+ assert_equal(1, reader.format.channels)
625
+ assert_equal(8, reader.format.bits_per_sample)
626
+ assert_equal(44100, reader.format.sample_rate)
627
+ assert_equal([:front_center], reader.format.speaker_mapping)
628
+ assert_equal(false, reader.closed?)
629
+ assert_equal(0, reader.current_sample_frame)
630
+ assert_equal(2240, reader.total_sample_frames)
631
+ assert_equal(true, reader.readable_format?)
632
+ assert_nil(reader.sampler_info)
633
+ reader.close
634
+
635
+ buffers = read_file("valid/valid_format_chunk_extra_bytes_with_odd_size_and_padding_byte.wav", 1024)
636
+
637
+ assert_equal(3, buffers.length)
638
+ assert_equal([1024, 1024, 192], buffers.map {|buffer| buffer.samples.length })
639
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 128, buffers[0].samples)
640
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 128, buffers[1].samples)
641
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 24, buffers[2].samples)
642
+ end
643
+
644
+ def test_read_float_format_chunk_with_oversized_extension
645
+ reader = Reader.new(fixture("valid/valid_float_format_chunk_oversized_extension.wav"))
646
+
647
+ assert_equal(3, reader.native_format.audio_format)
648
+ assert_equal(1, reader.native_format.channels)
649
+ assert_equal(32, reader.native_format.bits_per_sample)
650
+ assert_equal(44100, reader.native_format.sample_rate)
651
+ assert_nil(reader.native_format.speaker_mapping)
652
+ assert_nil(reader.native_format.sub_audio_format_guid)
653
+ assert_nil(reader.native_format.valid_bits_per_sample)
654
+ assert_equal(1, reader.format.channels)
655
+ assert_equal(32, reader.format.bits_per_sample)
656
+ assert_equal(44100, reader.format.sample_rate)
657
+ assert_equal([:front_center], reader.format.speaker_mapping)
658
+ assert_equal(false, reader.closed?)
659
+ assert_equal(0, reader.current_sample_frame)
660
+ assert_equal(2240, reader.total_sample_frames)
661
+ assert_equal(true, reader.readable_format?)
662
+ assert_nil(reader.sampler_info)
663
+ reader.close
664
+
665
+ buffers = read_file("valid/valid_float_format_chunk_oversized_extension.wav", 1024)
666
+
667
+ assert_equal(3, buffers.length)
668
+ assert_equal([1024, 1024, 192], buffers.map {|buffer| buffer.samples.length })
669
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:float] * 128, buffers[0].samples)
670
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:float] * 128, buffers[1].samples)
671
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:float] * 24, buffers[2].samples)
672
+ end
673
+
674
+ def test_read_float_format_chunk_with_extra_bytes
675
+ reader = Reader.new(fixture("valid/valid_float_format_chunk_with_extra_bytes.wav"))
676
+
677
+ assert_equal(3, reader.native_format.audio_format)
678
+ assert_equal(1, reader.native_format.channels)
679
+ assert_equal(32, reader.native_format.bits_per_sample)
680
+ assert_equal(44100, reader.native_format.sample_rate)
681
+ assert_nil(reader.native_format.speaker_mapping)
682
+ assert_nil(reader.native_format.sub_audio_format_guid)
683
+ assert_nil(reader.native_format.valid_bits_per_sample)
684
+ assert_equal(1, reader.format.channels)
685
+ assert_equal(32, reader.format.bits_per_sample)
686
+ assert_equal(44100, reader.format.sample_rate)
687
+ assert_equal([:front_center], reader.format.speaker_mapping)
688
+ assert_equal(false, reader.closed?)
689
+ assert_equal(0, reader.current_sample_frame)
690
+ assert_equal(2240, reader.total_sample_frames)
691
+ assert_equal(true, reader.readable_format?)
692
+ assert_nil(reader.sampler_info)
693
+ reader.close
694
+
695
+ buffers = read_file("valid/valid_float_format_chunk_with_extra_bytes.wav", 1024)
696
+
697
+ assert_equal(3, buffers.length)
698
+ assert_equal([1024, 1024, 192], buffers.map {|buffer| buffer.samples.length })
699
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:float] * 128, buffers[0].samples)
700
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:float] * 128, buffers[1].samples)
701
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:float] * 24, buffers[2].samples)
702
+ end
703
+
704
+ def test_read_float_format_chunk_with_oversized_extension_and_extra_bytes
705
+ reader = Reader.new(fixture("valid/valid_float_format_chunk_oversized_extension_and_extra_bytes.wav"))
706
+
707
+ assert_equal(3, reader.native_format.audio_format)
708
+ assert_equal(1, reader.native_format.channels)
709
+ assert_equal(32, reader.native_format.bits_per_sample)
710
+ assert_equal(44100, reader.native_format.sample_rate)
711
+ assert_nil(reader.native_format.speaker_mapping)
712
+ assert_nil(reader.native_format.sub_audio_format_guid)
713
+ assert_nil(reader.native_format.valid_bits_per_sample)
714
+ assert_equal(1, reader.format.channels)
715
+ assert_equal(32, reader.format.bits_per_sample)
716
+ assert_equal(44100, reader.format.sample_rate)
717
+ assert_equal([:front_center], reader.format.speaker_mapping)
718
+ assert_equal(false, reader.closed?)
719
+ assert_equal(0, reader.current_sample_frame)
720
+ assert_equal(2240, reader.total_sample_frames)
721
+ assert_equal(true, reader.readable_format?)
722
+ assert_nil(reader.sampler_info)
723
+ reader.close
724
+
725
+ buffers = read_file("valid/valid_float_format_chunk_with_extra_bytes.wav", 1024)
726
+
727
+ assert_equal(3, buffers.length)
728
+ assert_equal([1024, 1024, 192], buffers.map {|buffer| buffer.samples.length })
729
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:float] * 128, buffers[0].samples)
730
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:float] * 128, buffers[1].samples)
731
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:float] * 24, buffers[2].samples)
732
+ end
733
+
734
+ def test_read_extensible_format_chunk_with_oversized_extension
735
+ reader = Reader.new(fixture("valid/valid_extensible_format_chunk_oversized_extension.wav"))
736
+
737
+ assert_equal(65534, reader.native_format.audio_format)
738
+ assert_equal(1, reader.native_format.channels)
739
+ assert_equal(8, reader.native_format.bits_per_sample)
740
+ assert_equal(44100, reader.native_format.sample_rate)
741
+ assert_equal(8, reader.native_format.valid_bits_per_sample)
742
+ assert_equal([:front_center], reader.native_format.speaker_mapping)
743
+ assert_equal(WaveFile::SUB_FORMAT_GUID_PCM, reader.native_format.sub_audio_format_guid)
744
+ assert_equal(1, reader.format.channels)
745
+ assert_equal(8, reader.format.bits_per_sample)
746
+ assert_equal(44100, reader.format.sample_rate)
747
+ assert_equal([:front_center], reader.format.speaker_mapping)
748
+ assert_equal(false, reader.closed?)
749
+ assert_equal(0, reader.current_sample_frame)
750
+ assert_equal(2240, reader.total_sample_frames)
751
+ assert_equal(true, reader.readable_format?)
752
+ assert_nil(reader.sampler_info)
753
+ reader.close
754
+
755
+ buffers = read_file("valid/valid_extensible_format_chunk_oversized_extension.wav", 1024)
756
+
757
+ assert_equal(3, buffers.length)
758
+ assert_equal([1024, 1024, 192], buffers.map {|buffer| buffer.samples.length })
759
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 128, buffers[0].samples)
760
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 128, buffers[1].samples)
761
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 24, buffers[2].samples)
762
+ end
763
+
764
+ def test_read_extensible_format_chunk_with_extra_bytes
765
+ reader = Reader.new(fixture("valid/valid_extensible_format_chunk_with_extra_bytes.wav"))
766
+
767
+ assert_equal(65534, reader.native_format.audio_format)
768
+ assert_equal(1, reader.native_format.channels)
769
+ assert_equal(8, reader.native_format.bits_per_sample)
770
+ assert_equal(44100, reader.native_format.sample_rate)
771
+ assert_equal(8, reader.native_format.valid_bits_per_sample)
772
+ assert_equal([:front_center], reader.native_format.speaker_mapping)
773
+ assert_equal(WaveFile::SUB_FORMAT_GUID_PCM, reader.native_format.sub_audio_format_guid)
774
+ assert_equal(8, reader.native_format.valid_bits_per_sample)
775
+ assert_equal(1, reader.format.channels)
776
+ assert_equal(8, reader.format.bits_per_sample)
777
+ assert_equal(44100, reader.format.sample_rate)
778
+ assert_equal([:front_center], reader.format.speaker_mapping)
779
+ assert_equal(false, reader.closed?)
780
+ assert_equal(0, reader.current_sample_frame)
781
+ assert_equal(2240, reader.total_sample_frames)
782
+ assert_equal(true, reader.readable_format?)
783
+ assert_nil(reader.sampler_info)
784
+ reader.close
785
+
786
+ buffers = read_file("valid/valid_extensible_format_chunk_with_extra_bytes.wav", 1024)
787
+
788
+ assert_equal(3, buffers.length)
789
+ assert_equal([1024, 1024, 192], buffers.map {|buffer| buffer.samples.length })
790
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 128, buffers[0].samples)
791
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 128, buffers[1].samples)
792
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 24, buffers[2].samples)
793
+ end
794
+
795
+ def test_read_extensible_format_chunk_with_oversized_extension_and_extra_bytes
796
+ reader = Reader.new(fixture("valid/valid_extensible_format_chunk_oversized_extension_and_extra_bytes.wav"))
797
+
798
+ assert_equal(65534, reader.native_format.audio_format)
799
+ assert_equal(1, reader.native_format.channels)
800
+ assert_equal(8, reader.native_format.bits_per_sample)
801
+ assert_equal(44100, reader.native_format.sample_rate)
802
+ assert_equal(8, reader.native_format.valid_bits_per_sample)
803
+ assert_equal([:front_center], reader.native_format.speaker_mapping)
804
+ assert_equal(WaveFile::SUB_FORMAT_GUID_PCM, reader.native_format.sub_audio_format_guid)
805
+ assert_equal(8, reader.native_format.valid_bits_per_sample)
806
+ assert_equal(1, reader.format.channels)
807
+ assert_equal(8, reader.format.bits_per_sample)
808
+ assert_equal(44100, reader.format.sample_rate)
809
+ assert_equal([:front_center], reader.format.speaker_mapping)
810
+ assert_equal(false, reader.closed?)
811
+ assert_equal(0, reader.current_sample_frame)
812
+ assert_equal(2240, reader.total_sample_frames)
813
+ assert_equal(true, reader.readable_format?)
814
+ assert_nil(reader.sampler_info)
815
+ reader.close
816
+
817
+ buffers = read_file("valid/valid_extensible_format_chunk_with_extra_bytes.wav", 1024)
818
+
819
+ assert_equal(3, buffers.length)
820
+ assert_equal([1024, 1024, 192], buffers.map {|buffer| buffer.samples.length })
821
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 128, buffers[0].samples)
822
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 128, buffers[1].samples)
823
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_8] * 24, buffers[2].samples)
824
+ end
825
+
420
826
  def test_read_with_format_conversion
421
827
  buffers = read_file("valid/valid_mono_pcm_16_44100.wav", 1024, Format.new(:stereo, :pcm_8, 22100))
422
828
 
@@ -531,6 +937,21 @@ class ReaderTest < Minitest::Test
531
937
  assert_equal(2241, reader.total_sample_frames)
532
938
  end
533
939
 
940
+ def test_each_buffer_not_at_beginning_of_file
941
+ reader = Reader.new(fixture("valid/valid_mono_pcm_16_44100.wav"))
942
+
943
+ buffers = []
944
+ reader.read(8)
945
+ reader.each_buffer {|buffer| buffers << buffer }
946
+
947
+ assert(reader.closed?)
948
+ assert_equal(1, buffers.length)
949
+ assert_equal([2232], buffers.map {|buffer| buffer.samples.length })
950
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_16] * 279, buffers[0].samples)
951
+ assert_equal(2240, reader.current_sample_frame)
952
+ assert_equal(2240, reader.total_sample_frames)
953
+ end
954
+
534
955
  def test_each_buffer_inside_reader_block
535
956
  buffers = []
536
957
 
@@ -546,6 +967,21 @@ class ReaderTest < Minitest::Test
546
967
  assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_16] * 24, buffers[2].samples)
547
968
  end
548
969
 
970
+ def test_read_after_each_buffer_inside_block_raises_error
971
+ buffers = []
972
+
973
+ Reader.new(fixture("valid/valid_mono_pcm_16_44100.wav")) do |reader|
974
+ reader.each_buffer(1024) {|buffer| buffers << buffer }
975
+ assert_raises(ReaderClosedError) { reader.read(100) }
976
+ end
977
+
978
+ assert_equal(3, buffers.length)
979
+ assert_equal([1024, 1024, 192], buffers.map {|buffer| buffer.samples.length })
980
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_16] * 128, buffers[0].samples)
981
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_16] * 128, buffers[1].samples)
982
+ assert_equal(SQUARE_WAVE_CYCLE[:mono][:pcm_16] * 24, buffers[2].samples)
983
+ end
984
+
549
985
  def test_read_non_data_chunk_with_padding_byte
550
986
  # This fixture file contains a JUNK chunk with an odd size, aligned to an even number of
551
987
  # bytes via an appended padding byte. If the padding byte is not taken into account, this
@@ -621,35 +1057,35 @@ class ReaderTest < Minitest::Test
621
1057
 
622
1058
  assert_equal(0, reader.current_sample_frame)
623
1059
  assert_equal(2240, reader.total_sample_frames)
624
- test_duration({:hours => 0, :minutes => 0, :seconds => 0, :milliseconds => 50, :sample_count => 2240},
1060
+ test_duration({hours: 0, minutes: 0, seconds: 0, milliseconds: 50, sample_count: 2240},
625
1061
  reader.total_duration)
626
1062
 
627
1063
 
628
1064
  reader.read(1024)
629
1065
  assert_equal(1024, reader.current_sample_frame)
630
1066
  assert_equal(2240, reader.total_sample_frames)
631
- test_duration({:hours => 0, :minutes => 0, :seconds => 0, :milliseconds => 50, :sample_count => 2240},
1067
+ test_duration({hours: 0, minutes: 0, seconds: 0, milliseconds: 50, sample_count: 2240},
632
1068
  reader.total_duration)
633
1069
 
634
1070
 
635
1071
  reader.read(1024)
636
1072
  assert_equal(2048, reader.current_sample_frame)
637
1073
  assert_equal(2240, reader.total_sample_frames)
638
- test_duration({:hours => 0, :minutes => 0, :seconds => 0, :milliseconds => 50, :sample_count => 2240},
1074
+ test_duration({hours: 0, minutes: 0, seconds: 0, milliseconds: 50, sample_count: 2240},
639
1075
  reader.total_duration)
640
1076
 
641
1077
 
642
1078
  reader.read(192)
643
1079
  assert_equal(2240, reader.current_sample_frame)
644
1080
  assert_equal(2240, reader.total_sample_frames)
645
- test_duration({:hours => 0, :minutes => 0, :seconds => 0, :milliseconds => 50, :sample_count => 2240},
1081
+ test_duration({hours: 0, minutes: 0, seconds: 0, milliseconds: 50, sample_count: 2240},
646
1082
  reader.total_duration)
647
1083
 
648
1084
 
649
1085
  reader.close
650
1086
  assert_equal(2240, reader.current_sample_frame)
651
1087
  assert_equal(2240, reader.total_sample_frames)
652
- test_duration({:hours => 0, :minutes => 0, :seconds => 0, :milliseconds => 50, :sample_count => 2240},
1088
+ test_duration({hours: 0, minutes: 0, seconds: 0, milliseconds: 50, sample_count: 2240},
653
1089
  reader.total_duration)
654
1090
  end
655
1091
  end
@@ -1,9 +1,9 @@
1
1
  module WaveFileIOTestHelper
2
- CHANNEL_ALIAS = { :mono => 1, :stereo => 2, :tri => 3}
2
+ CHANNEL_ALIAS = {mono: 1, stereo: 2, tri: 3}
3
3
 
4
4
  SUPPORTED_BITS_PER_SAMPLE = {
5
- :pcm => [8, 16, 24, 32].freeze,
6
- :float => [32, 64].freeze,
5
+ pcm: [8, 16, 24, 32].freeze,
6
+ float: [32, 64].freeze,
7
7
  }.freeze
8
8
 
9
9
  SQUARE_WAVE_CYCLE = {}
data/test/writer_test.rb CHANGED
@@ -414,7 +414,7 @@ private
414
414
  dir = Dir.new(OUTPUT_FOLDER)
415
415
  file_names = dir.entries
416
416
  file_names.each do |file_name|
417
- if(file_name != "." && file_name != "..")
417
+ if file_name != "." && file_name != ".."
418
418
  File.delete("#{OUTPUT_FOLDER}/#{file_name}")
419
419
  end
420
420
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wavefile
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Strait
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-12-29 00:00:00.000000000 Z
11
+ date: 2022-12-30 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: You can use this gem to create Ruby programs that work with audio, by
14
14
  reading and writing Wave sound files (*.wav). Since it is written in pure Ruby (as
@@ -48,7 +48,20 @@ files:
48
48
  - test/fixtures/wave/invalid/data_chunk_truncated.wav
49
49
  - test/fixtures/wave/invalid/empty.wav
50
50
  - test/fixtures/wave/invalid/empty_format_chunk.wav
51
+ - test/fixtures/wave/invalid/extensible_format_chunk_extension_incomplete.wav
52
+ - test/fixtures/wave/invalid/extensible_format_chunk_extension_incomplete_in_incorrectly_sized_chunk.wav
53
+ - test/fixtures/wave/invalid/extensible_format_chunk_extension_incomplete_in_large_enough_chunk.wav
54
+ - test/fixtures/wave/invalid/extensible_format_chunk_extension_missing.wav
55
+ - test/fixtures/wave/invalid/extensible_format_chunk_extension_size_incomplete.wav
56
+ - test/fixtures/wave/invalid/extensible_format_chunk_extension_size_incomplete_with_padding_byte.wav
57
+ - test/fixtures/wave/invalid/extensible_format_chunk_extension_truncated.wav
58
+ - test/fixtures/wave/invalid/extensible_format_chunk_oversized_extension_too_large.wav
59
+ - test/fixtures/wave/invalid/float_format_chunk_extension_size_incomplete.wav
60
+ - test/fixtures/wave/invalid/float_format_chunk_extension_size_incomplete_with_padding_byte.wav
61
+ - test/fixtures/wave/invalid/float_format_chunk_oversized_extension_too_large.wav
51
62
  - test/fixtures/wave/invalid/format_chunk_after_data_chunk.wav
63
+ - test/fixtures/wave/invalid/format_chunk_extra_bytes_with_odd_size_and_missing_padding_byte.wav
64
+ - test/fixtures/wave/invalid/format_chunk_with_extra_byte_and_missing_padding_byte.wav
52
65
  - test/fixtures/wave/invalid/incomplete_riff_format.wav
53
66
  - test/fixtures/wave/invalid/incomplete_riff_header.wav
54
67
  - test/fixtures/wave/invalid/insufficient_format_chunk.wav
@@ -62,15 +75,27 @@ files:
62
75
  - test/fixtures/wave/invalid/smpl_chunk_loop_count_too_high.wav
63
76
  - test/fixtures/wave/invalid/smpl_chunk_truncated_sampler_specific_data.wav
64
77
  - test/fixtures/wave/invalid/truncated_smpl_chunk.wav
65
- - test/fixtures/wave/unsupported/bad_audio_format.wav
78
+ - test/fixtures/wave/invalid/unsupported_format_extension_size_incomplete.wav
79
+ - test/fixtures/wave/invalid/unsupported_format_extension_size_incomplete_with_padding_byte.wav
80
+ - test/fixtures/wave/invalid/unsupported_format_extension_truncated.wav
81
+ - test/fixtures/wave/invalid/unsupported_format_oversized_extension_too_large.wav
66
82
  - test/fixtures/wave/unsupported/bad_channel_count.wav
67
83
  - test/fixtures/wave/unsupported/bad_sample_rate.wav
68
84
  - test/fixtures/wave/unsupported/extensible_container_size_bigger_than_sample_size.wav
69
85
  - test/fixtures/wave/unsupported/extensible_unsupported_subformat_guid.wav
70
86
  - test/fixtures/wave/unsupported/unsupported_audio_format.wav
71
87
  - test/fixtures/wave/unsupported/unsupported_bits_per_sample.wav
88
+ - test/fixtures/wave/unsupported/unsupported_format_code_missing_extension_size.wav
89
+ - test/fixtures/wave/unsupported/unsupported_format_code_with_extension.wav
90
+ - test/fixtures/wave/unsupported/unsupported_format_code_with_extension_and_extra_bytes.wav
91
+ - test/fixtures/wave/unsupported/unsupported_format_code_with_incomplete_extension.wav
92
+ - test/fixtures/wave/unsupported/unsupported_format_code_with_oversized_extension.wav
93
+ - test/fixtures/wave/unsupported/unsupported_format_code_with_oversized_extension_and_extra_bytes.wav
72
94
  - test/fixtures/wave/valid/no_samples.wav
73
95
  - test/fixtures/wave/valid/valid_extensible_20_pcm_16_44100_speaker_mapping_overflow.wav
96
+ - test/fixtures/wave/valid/valid_extensible_format_chunk_oversized_extension.wav
97
+ - test/fixtures/wave/valid/valid_extensible_format_chunk_oversized_extension_and_extra_bytes.wav
98
+ - test/fixtures/wave/valid/valid_extensible_format_chunk_with_extra_bytes.wav
74
99
  - test/fixtures/wave/valid/valid_extensible_mono_float_32_44100.wav
75
100
  - test/fixtures/wave/valid/valid_extensible_mono_float_64_44100.wav
76
101
  - test/fixtures/wave/valid/valid_extensible_mono_pcm_16_44100.wav
@@ -97,6 +122,13 @@ files:
97
122
  - test/fixtures/wave/valid/valid_extensible_tri_pcm_24_44100.wav
98
123
  - test/fixtures/wave/valid/valid_extensible_tri_pcm_32_44100.wav
99
124
  - test/fixtures/wave/valid/valid_extensible_tri_pcm_8_44100.wav
125
+ - test/fixtures/wave/valid/valid_float_format_chunk_missing_extension_size.wav
126
+ - test/fixtures/wave/valid/valid_float_format_chunk_oversized_extension.wav
127
+ - test/fixtures/wave/valid/valid_float_format_chunk_oversized_extension_and_extra_bytes.wav
128
+ - test/fixtures/wave/valid/valid_float_format_chunk_with_extra_bytes.wav
129
+ - test/fixtures/wave/valid/valid_format_chunk_extra_bytes_with_odd_size_and_padding_byte.wav
130
+ - test/fixtures/wave/valid/valid_format_chunk_with_extra_byte_and_padding_byte.wav
131
+ - test/fixtures/wave/valid/valid_format_chunk_with_extra_bytes.wav
100
132
  - test/fixtures/wave/valid/valid_mono_float_32_44100.wav
101
133
  - test/fixtures/wave/valid/valid_mono_float_64_44100.wav
102
134
  - test/fixtures/wave/valid/valid_mono_pcm_16_44100.wav
@@ -133,12 +165,12 @@ files:
133
165
  - test/unvalidated_format_test.rb
134
166
  - test/wavefile_io_test_helper.rb
135
167
  - test/writer_test.rb
136
- homepage: http://wavefilegem.com/
168
+ homepage: https://wavefilegem.com/
137
169
  licenses:
138
170
  - MIT
139
171
  metadata: {}
140
172
  post_install_message: Thanks for installing the WaveFile gem! For documentation and
141
- examples, visit http://wavefilegem.com
173
+ examples, visit https://wavefilegem.com
142
174
  rdoc_options: []
143
175
  require_paths:
144
176
  - lib
@@ -153,8 +185,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
153
185
  - !ruby/object:Gem::Version
154
186
  version: '0'
155
187
  requirements: []
156
- rubygems_version: 3.1.2
157
- signing_key:
188
+ rubygems_version: 3.4.1
189
+ signing_key:
158
190
  specification_version: 4
159
191
  summary: A pure Ruby library for reading and writing Wave sound files (*.wav)
160
192
  test_files: []