jstrait / wavefile Goto Github PK
View Code? Open in Web Editor NEWA Ruby gem for reading and writing sound files in Wave format (*.wav)
Home Page: https://wavefilegem.com
License: MIT License
A Ruby gem for reading and writing sound files in Wave format (*.wav)
Home Page: https://wavefilegem.com
License: MIT License
If I'm not wrong, both examples below are supposed to work just as well. However, the second test produces a WAV file with the correct size, but somehow it's corrupted.
require "wavefile"
# Test 1, this works
reader = WaveFile::Reader.new "test.wav"
WaveFile::Writer.new("output1.wav", reader.format) do |writer|
reader.each_buffer(4096) do |buffer|
writer.write WaveFile::Buffer.new(buffer.samples, reader.format)
end
end
# Test 2, output corrupted
reader = WaveFile::Reader.new "test.wav"
writer = WaveFile::Writer.new("output2.wav", reader.format)
reader.each_buffer(4096) do |buffer|
writer.write WaveFile::Buffer.new(buffer.samples, reader.format)
end
For example:
Writer.new("error.wav", format) do |writer|
writer.write(buffer)
x = 1 / 0
end
When this is run, the error.wav
will exist on disk, but will not be playable because the Writer
was never closed.
Adding an ensure
clause to the block_given?
section of Writer.new
should resolve this.
It would be nice if the interface would allow streaming when reading a wave file.
e.g.
wav_reader = WaveFile::Reader.new(File.open("some.wav"))
or even more generically from any Ruby IO object:
wav_reader = WaveFile::Reader.new(StringIO.new(".... wave bytes..."))
i have wav and need reverse file
how read file and add noise or add other file wav?
Feature request: I'd love to be able to ask some high-level questions about a file, like:
sound = WaveFile::Reader.new('StartUp.wav')
puts "Duration in milliseconds: #{sound.duration}",
"Stereo? #{sound.channels==2}"
Consider the following code:
require 'wavefile'
include WaveFile
#1 second 440 Hz square wave
format = Format.new(:mono, 16, 44100)
writer = Writer.new("square.wav", format)
cycle = ([2**15] * 50) + ([-2**15] * 50)
buffer = Buffer.new(cycle, format)
441.times do
writer.write(buffer)
end
writer.close()
# square.wav now has 44100 samples
# read square wave in at half the sampling rate
samples = []
format = Format.new(:mono, 16, 22050)
reader = Reader.new("square.wav", format).each_buffer(1024) do |buffer|
samples += buffer.samples
end
puts "#{samples.length} samples read"
# outputs "44100 samples read"
I would assume to get 22050 samples instead of 44100, thus a resampled version of the file. Am i the only one feeling this way?
Hello!
I was trying to use this gem to record a wav file to STDOUT and then feed it to WaveFile::Reader
to analyze in realtime. If I recall correctly, it was something along the lines of
IO.popen("rec -c 1 -t wav - 2>/dev/null") do |stdout|
WaveFile::Reader.new(stdout) do |reader|
# do stuff
end
end
However, this results in the following exception:
.../wavefile-1.1.1/lib/wavefile/chunk_readers/riff_reader.rb:31:in `pos': Illegal seek (Errno::ESPIPE)
from .../wavefile-1.1.1/lib/wavefile/chunk_readers/riff_reader.rb:31:in `read_until_data_chunk'
from .../wavefile-1.1.1/lib/wavefile/chunk_readers/riff_reader.rb:10:in `initialize'
from .../wavefile-1.1.1/lib/wavefile/reader.rb:45:in `new'
from .../wavefile-1.1.1/lib/wavefile/reader.rb:45:in `initialize'
...
which is because it's trying to seek on the IO, which is an invalid operation on a pipe.
I did also try record to a file instead of STDOUT and calling WaveFile::Reader on that, but because the file size kept increasing, that also resulted in something going wrong (I think because WaveFile::Reader#read_until_data_chunk
tries to read until the end of the file). Also that means that the file keeps growing while the recording is ongoing, which is not ideal.
I ended up calling IO.popen("rec -c 1 -t s32 - 2>/dev/null")
which outputs the audio amplitudes as signed 32-bit integers, and analyzing that directly.
The reason I'm opening this ticket is because I'm wondering whether analyzing an audio recording in realtime is an aim of this project, and if so, whether it's a planned feature or something that can be done now and I just missed something?
Thank you!
A new release of the wavefile gem would be great! Not sure what the current plans are. Joel? Need help?
Is wavefile able to read cue/marker points from wave files. I have seen very little on this topic and am curious if wavefile would support this.
I think you may not be handling adding or reading an extra pad byte when a chunk has a size that is an odd number of bytes. Not all wav creators seem to do this but adobe audition does, and it leads to misaligned parsing of the riff chunks.
I tested such a file with your gem and it choked on it.
Here is more info from a similarly reported issue:
If a *.wav file's fmt
chunk has a format code of 65534
(i.e. WAVE_FORMAT_EXTENSIBLE), it should have a fmt
chunk extension at least 22 bytes long. However, as of v1.1.1 sample data can't be read from a WAVE_FORMAT_EXTENSIBLE file where the chunk extension is larger than 22 bytes. Although a Reader
instance can be created for such a file, the Reader.format.sub_audio_format_guid
field will have an incorrect value, and Reader.readable_format?
will be set to false
. Any attempts to read sample data will raise an error.
The "sub format GUID" field is 16 bytes long and exists at bytes 6-21 (0-based) of the chunk extension. The bug is that the gem reads from byte 6 until the end of the chunk extension to get this value, instead of just bytes 6-21. If the extension is larger than 22 bytes, this results in a value that is larger than 16 bytes, and is not recognized as valid. Since this value is needed to know how to read sample data out of the data
chunk, no sample data can be read from the file.
You might ask, is a WAVE_FORMAT_EXTENSIBLE chunk extension larger than 22 bytes even valid? Yes, according to the document that defines the WAVE_FORMAT_EXTENSIBLE format (https://docs.microsoft.com/en-us/previous-versions/windows/hardware/design/dn653308(v=vs.85)). It states that the chunk extension must be at least 22 bytes long. (Search for the "cbSize is at Least 22" heading in that document for more info). This implies that a WAVE_FORMAT_EXTENSIBLE file with a format chunk extension larger than 22 bytes should be valid and readable, with the extra bytes beyond the first 22 being ignored. (As long as the main fmt
chunk size also correctly accounts for the oversized extension).
Note that this is similar to the bug described in #34, but not the same. That bug refers to Reader
instances incorrectly being able to be created for WAVE_FORMAT_EXTENSIBLE files with a format chunk extension shorter than 22 bytes, instead of raising InvalidFormatError
.
This is also similar but not the same as the bug described in #33. That bug refers to issues caused by extra bytes occurring after the format chunk extension, while this issue refers to extra bytes inside the format chunk extension. The behavior is not the same for these two cases.
Hey
I found a small gist to get fft values of sound files.
It used these methods, samples = w.sample_data[0, [w.sample_rate * 10, w.sample_data.size].min]
Now, the sample_data
was available in the very first release of this gem.
What is the equivalent newer version to these methods.
Simple generating
reader.each_buffer do |buffer|
puts buffer.samples
end
returns different result
Two Durations that specify the exact same duration do not evaluate as equal.
Thank you for your work on this. I really like this project. I have however noticed some odd behavior. When I write a file, if I attempt to overwrite, no changes are recorded. Oddly, even if I delete the file and allow the application to recreate it, the data from the previous file is recreated. However, if I change the file name, the new data is written to file. I'm not sure if I'm doing something wrong or if there's a bug.
Here is the application I'm working on:
require 'wavefile'
include WaveFile
sample_rate = 44100
buffer_size = sample_rate * 5
buffer_data = Array.new(buffer_size)
class Frequency_node
def initialize(sample_rate, frequency)
@sample_rate = sample_rate
@frequency = sample_rate/frequency.to_f
@current_frame = 0
end
def next()
return_tone = Math.sin(((@current_frame % @frequency)/@frequency) * 2 * Math::PI)
@current_frame += 1
return return_tone
end
end
f = Frequency_node.new(sample_rate, 440.0)
(0...buffer_size).each do |frame|
buffer_data[frame] = f.next
end
format = Format.new(:mono, :pcm_16, 44100)
writer = Writer.new("a_440.wav", format)
buffer = Buffer.new(buffer_data, Format.new(:mono, :float, sample_rate))
writer.write(buffer)
writer.close()
After recording the 440 tone, if I then change it, the old tone is still recorded, even if I delete the file and allow the application to recreate it. However, if I rename the file i.e. "a_440_2.wav", the new tone will be recorded.
I'm using Ruby 1.9.3 and wavefile 0.5.0 on OSX 10.8.2
Thanks again.
When trying to read a wav file with WAVEFORMATEX encoding (format code 0xfffe/65534), wavefile bails out with:
Audio format is 65534, but only format code 1 (PCM) or 3 (floating point) is supported. (WaveFile::UnsupportedFormatError)
Hi,
just fyi: the wavefile gem is being used by Sonic Pi to teach coding to kids. In an effort to bring Sonic Pi to Debian, the wavefile gem has now been packaged: ruby-wavefile at Debian.
Thanks for making this gem.
I Want play file
Hi. I am receiving bytes for the linear16 wav file.
Can I somehow format it to mulaw/8000 without creating or saving a file?
And if not, is this format change possible at all?
How play buffer in real-time, without writing it to a file? Ruby 2.0.0 Windows 7 x64
@jstrait sorry if this is a total newbie issue, but when I run the example here - https://github.com/jstrait/wavefile/wiki/WaveFile-Tutorial#copying-a-wave-file-to-different-format with a voice recording, the resulting audio file sound like chipmunks. Here's my code example. Any clue on what I am doing wrong? Great gem, love working with it so far.
require 'wavefile'
include WaveFile
new_file = 'data/copy.wav'
Writer.new(new_file, Format.new(:mono, :pcm_16, 16000)) do |writer|
Reader.new('data/twilio_recording.wav').each_buffer(8000) do |buffer|
writer.write(buffer)
end
end
How can I rewind the Reader so that I can go back and read the first buffer? I'm trying to loop a wave file but currently the only way is to recreate the Reader object once I get to the end of the samples, which is expensive.
each_buffer
closes the reader which causes some unintuitive errors:
reader = WaveFile::Reader.new(ARGV.first)
reader.each_buffer do |buffer|
puts "Reading buffer ..."
end
reader.close # throws errors since reader is already closed
This also means you can never use each_buffer
in a Reader
block:
WaveFile::Reader.new(ARGV.first) do |reader|
reader.each_buffer do |buffer|
puts "Reading buffer ..."
end
end # error thrown when block completes
I personally don't think each_buffer
should close the reader. What if I want to make a second pass?
If it is important that each_block
closes the Reader
, then it should be renamed each_block!
since it has destructive side-effects. If each_block
is going to continue to close the Reader
, then you also won't wantReader#close
to throw an error when called on a closed Reader
or you'll never be able to use each_block
in a Reader
block.
Hi!
Thank you for working on this excellent library!
I am getting an UnsupportedFormatError when reading the included file. Is this truly an unsupported file format, or am I maybe doing something wrong when reading it?
The exception I get does not include a message with any details of the format. That would be nice.
The Writer class has an attr_reader for :file_name, but the @file_name instance variable never gets set. So a Writer instance returns nil when file_name is called.
It would be quite nice to be able to append with the Writer class, instead of always overriding an existing file.
Checking out the Writer class, it's hard-coded to open the given file with "wb" access mode. This could surely be extended a bit?
Normally, the "fmt "
chunk has an expected size based on the format code. For example:
1
(Integer PCM): 16 bytes3
(Floating point PCM): 18 bytes65534
(WAVE_FORMAT_EXTENSIBLE): 40 bytesWhen attempting to read a file using Reader.new()
, an InvalidFormatError
will be raised if the "fmt "
chunk has fewer bytes than the expected size, because the chunk has incomplete data.
However (as of v1.1.1), if a "fmt "
chunk has more bytes than expected, then an InvalidFormatError
will also sometimes be raised with the message "Not a supported wave file. The format chunk extension is shorter than expected."
. This error message is misleading, because the problem is that the chunk is longer than expected, not shorter. If the format code is 1
, it's doubly misleading because the "fmt "
chunk won't even have an extension.
Why is this error only sometimes raised? There is a scenario in which this error won't be raised if the "fmt "
chunk is larger than the normal expected size: if the format code is 1
, and the value of bytes 16 and 17 (0-based) when interpreted as a 16-bit unsigned little endian integer is the same as the subsequent number of bytes in the chunk body, no error will be raised. For example, if the "fmt "
chunk size is 22
, the format code is 1
, and the value of bytes 16 and 17 is 4
(when interpreted as a 16-bit unsigned little endian integer), the file can be opened correctly. Note that this implies that if the format code is not 1, the required chunk extension can contain extra bytes, but there can't be extra bytes following the extension. (However, if the file is in WAVE_FORMAT_EXTENSIBLE format (i.e. format code 65534
) and the extension has extra bytes, the "sub format GUID" field won't be read correctly; see #37 for more info on that issue).
In general, it seems like an error shouldn't be raised if the "fmt "
chunk is larger than the default expected size. Page 60 of the original "Multimedia Programming Interface and Data Specifications 1.0" document with the original definition of the *.wav file spec states:
"The < format-specific-fields> consists of zero or more bytes of parameters. Which parameters occur depends on the WAVE format category–see the following section for details. Playback software should be written to allow for (and ignore) any unknown < format-specific-fields> parameters that occur at the end of this field."
Which seems like it could be interpreted as meaning extra bytes at the end of a format chunk should be ignored without raising an error.
See #24 for more info on the background of this issue.
As an example, suppose a "fmt "
chunk has a stated size of 30 bytes, and the format code is not 1
(and therefore the chunk should have an extension). Since a "fmt "
chunk extension body always starts at byte 18 (0-based), this means there are 12 bytes available for the chunk extension. If the extension has a reported size larger than 12 bytes, then the chunk extension will overflow out of the chunk.
In v1.1.1, this scenario will always cause InvalidFormatError
to be raised. However, that happens by an accident of the implementation, and the error message will be misleading: "Not a supported wave file. The format chunk extension is shorter than expected."
Although the extension could be shorter than expected, that's not guaranteed, and won't really be the source of the error.
What should happen in this scenario? If some of the bytes that overflow are part of required fields for the chunk extension (such as say, the first 22 bytes of a WAVE_FORMAT_EXTENSIBLE chunk extension), then it seems clear this should result in an error. If the overflowed bytes are just extra inert bytes, then you could argue that no error needs to be raised. However, that could lead to inconsistent behavior between different format codes, because the gem would have to understand the chunk extension format of every one of the many possible format codes in order to implement this in a consistent way. Also, always raising an error is how previous versions of the gem behave. Therefore, it seems preferable to always raise an error if the chunk extension overflows.
This is closely related to #33, because that bug and this one are caused by the same line of code. Despite the overlap, this bug is being opened since the two issues seem distinct from a behavior perspective.
When a fmt
chunk has the format code 65534
(i.e. WAVE_FORMAT_EXTENSIBLE) it is required to have a chunk extension of 22 bytes containing 3 fields. These fields, especially the final 16 byte "audio format GUID" field, are required in order to correctly read the file's sample data, and don't have sensible default values.
In v1.1.1 of the gem a Reader
instance can be created, without any error being raised, for some WAVE_FORMAT_EXTENSIBLE files in which the fmt
chunk has a missing/truncated extension. The readable_format?
field on the Reader
instance will be correctly set to false, so no sample data will be able to be read out of it. However, the object attached to format
and native_format
will contain invalid values (i.e. either nil
or ""
) for the missing extension fields.
It seems like a bug that Reader
instances can be created from files like this. These are malformed files, and some of the information shown in the objects returned by format
and native_format
will not necessarily be meaningful/accurate. It seems like better behavior would be to instead raise an InvalidFormatError
when trying to read this type of file, and not allow a Reader
instance to be created.
Hello,
This gem is useful and I'm very thanks to you.
I have one question.
Using your gem, can I mix 2 wave files and set the volume for each track?
Looking forward your answer.
Hi All,
Is there strictly no way that WaveInfo will support 24 bits per sample .wav audio tracks? I am pretty deep into the process of building a web application that needs to be able to convert 24 bits per sample files into 192/320 MP3's. At the moment I am using LAME as my Encoder of choice.
I have tried to research into this a bit further but I am no audio expert so it's all a bit confusing!
Any help on the matter would be greatly received :)
Edit: I have tested the process of reading a 24-bit file as a 32-bit file, but this simply throws an "UnsupportedFormatError" exception.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.