A Ruby gem for reading and writing sound files in Wave format (*.wav)
MIT License
Bot releases are hidden (Show)
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.
The full details:
Bug Fix: Files that have extra bytes at the end of the "fmt "
chunk can now be read.
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).
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.
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.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.Bug Fix: Files in WAVE_FORMAT_EXTENSIBLE format with a missing or incomplete "fmt "
chunk extension can no longer be opened using Reader.new
.
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.
Bug Fix: Files in WAVE_FORMAT_EXTENSIBLE format that have extra bytes at the end of the "fmt "
chunk extension can now be read.
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.
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.
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.
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.
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).
Published by jstrait almost 5 years ago
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).Published by jstrait over 5 years ago
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 for suggesting this feature and providing the base implementation.Reader.new
. When attempting to read an invalid file, the error message now provides more detail about why the file is invalid.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).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).Format#sample rate
. The correct maximum sample rate is now 4_294_967_295; previously it allowed a maximum of 4_294_967_296.Published by jstrait about 6 years ago
Bug Fix: the file(s) written to an arbitrary IO instance are no longer corrupt if the initial seek position is greater than 0.
Published by jstrait over 6 years ago
Reader.close
on a Reader
instance that is already closed no longer raises ReaderClosedError
. Instead, it does nothing. Similarly, calling Writer.close
on a Writer
instance that is already closed no longer raises WriterClosedError
. Thanks to @kylekyle for raising this as an issue.Writer
will now write files using a format called WAVEFORMATEXTENSIBLE
where appropriate. This is a behind-the-scenes improvement - for most use cases it won't affect how you use the gem, but can result in better compatibility with other programs.
WAVEFORMATEXTENSIBLE
format if any of the following are true:
:pcm_24
or :pcm_32
).Reader.format.speaker_mapping
field.
reader = Reader.new("4_channel_file.wav")
# [:front_left, :front_right, :front_center, :back_center]
puts reader.format.speaker_mapping.inspect
WAVEFORMATEXTENSIBLE
format). For a non-WAVEFORMATEXTENSIBLE
file, Reader.native_format.speaker_mapping
will be nil
, to reflect that the channel->speaker mapping is undefined. Reader.format.speaker_mapping
will use a "sensible" default value for the given number of channels.Format
instance. If not given, a default value will be set for the given number of channels.
Format.new(4, :pcm_16, 44100, speaker_mapping: [:front_left, :front_right, :front_center, :low_frequency])
Format.new
are improved to provide more detail.Published by jstrait over 6 years ago
--enable-frozen-string-literal
Ruby option is enabled. Thanks to @samaaron for finding and fixing this!Published by jstrait over 6 years ago
WAVEFORMATEXTENSIBLE
format (format code 65534) can now be read.
WAVEFORMATEXTENSIBLE
files. That is, PCM (8/16/24/32 bits per sample) or IEEE_FLOAT (32/64 bits per sample).WAVEFORMATEXTENSIBLE
format is not supported - all files will be written as a "vanilla" file regardless of the number of channels or sample format.Reader
and Writer
can now be constructed using with an open IO
instance, to allow reading/writing using an arbitrary IO-like object (File
, StringIO
, etc). Previously, they could only be constructed from a file name (given by a String
). Thanks to @taf2 for suggesting this feature and providing an example pull request.Reader.each_buffer()
is now optional. If not given, a default buffer size will be used.Duration
objects will now evaluate to equal if they represent the same amount of time, due to an overridden definition of ==
. Thanks to @chrylis for suggesting this improvement.ReaderClosedError
is now raised (instead of IOError
) when attempting to read from a closed Reader
instance. However, ReaderClosedError
extends IOError
, so this should be a backwards compatible change.WriterClosedError
is now raised (instead of IOError
) when attempting to read from a closed Writer
instance. However, WriterClosedError
extends IOError
, so this should be a backwards compatible change.Reader.file_name
and Writer.file_name
have been removed. When a Reader
or Writer
instance is constructed from an IO
instance, this field wouldn't necessarily have a sensible value. Since I don't know of an obvious use-case for these fields, going ahead and removing them altogether.Format
instance as an integer (implying PCM format, with the number being the bits per sample) has been removed. For example, this is no longer valid: Format.new(:mono, 16, 44100)
. Instead, use Format.new(:mono, :pcm_16, 44100)
.Published by jstrait over 6 years ago
Reader.native_format
. Returns a Format
instance with information about the underlaying format of the Wave file being read, which is not necessarily the same format the sample data is being converted to as it's being read.Reader.info()
has been removed. Instead, construct a new Reader
instance and use Reader.native_format()
- this will return a Format
instance with the same info that would have been returned by Reader.info()
.Info
class has been removed, due to Reader.info()
being removed.Reader
instance will no longer raise an exception if the file is valid Wave file, but in a format unsupported by this gem. The purpose of this is to allow calling Reader.native_format()
on this instance, to get format information for files not supported by this gem.Reader.readable_format?
returns true
if the file is a valid format that the gem can read, false
otherwise.Reader.read()
and Reader.each_buffer()
will now raise an exception if the file is a valid Wave file, but not a format that the gem can read. Or put differently, if Reader.readable_format?
returns false, any subsequent calls to Reader.read()
or Reader.each_buffer()
will raise an exception.Published by jstrait over 6 years ago
Published by jstrait over 6 years ago
Duration
object which can be used to calculate the playback time given a sample rate and number of sample frames.Reader.current_sample_frame
, Reader.total_sample_frames
, and Writer.total_sample_frames
.Duration
object as well: Reader.total_duration
, Writer.total_duration
.Format.new
now indicates the sample format, not the bits per sample. For example, :pcm_16
or :float_32
instead of 8 or 16. For backwards compatibility, 8, 16, and 32 can still be given and will be interpreted as :pcm_8
, :pcm_16
, and :pcm_32
, but this support might be removed in the future.Writer
block. (Thanks to James Tunnell (https://github.com/jamestunnell) for reporting and fixing this).Writer.file_name
now returns the file name, instead of always returning nil
(Thanks to James Tunnell (https://github.com/jamestunnell) for reporting this).Info.duration
now returns a Duration
object, instead of a hash.Info.sample_count
has been renamed sample_frame_count
.Published by jstrait over 6 years ago
IO.open()
works.Published by jstrait over 6 years ago
bits_per_sample=()
. Allows converting a file from 8-bit to 16-bit and vice-versa.num_channels=()
. Allows converting a mono file to stereo, and vice-versa.sample_rate=()
. Allows changing the sample rate of a file.duration()
. Returns a hash listing the playback time of the file.inspect()
. Returns a pretty-printed string listing metadata about the file.Published by jstrait over 6 years ago
Published by jstrait over 6 years ago
mono?()
and stereo?()
.:mono
and :stereo
into the num_channels
argument of the constructor, to allow for more readable code.reverse()
.Published by jstrait over 6 years ago
Initial release