WaveFile Gem

Reading Entire Wave File With a Block

This is a simple way to read an entire wave file:

require "wavefile"
include WaveFile

Reader.new("my_file.wav").each_buffer do |buffer|
  puts "Read #{buffer.samples.length} sample frames."
end

First construct a Reader object, then call each_buffer() on it. Successive sample buffers will be read from the file and passed to the given block, until all sample data in the file has been read. Finally, the Reader will automatically be closed. (Note that this is essentially the same as how IO.open works if you pass it a block).

Reading Wave File Manually

Alternately, you can manually call read() to control exactly how much of the file to read. When doing this, make sure to close the Reader when you’re done.

require "wavefile"
include WaveFile

SAMPLES_PER_BUFFER = 4096

reader = Reader.new("my_file.wav")
begin
  while reader.current_sample_frame < reader.total_sample_frames do
    buffer = reader.read(SAMPLES_PER_BUFFER)
    puts "Read #{buffer.samples.length} sample frames."
  end
rescue EOFError
  puts "Unexpected EOFError before reading all sample frames"
ensure
  reader.close
end

Reading a Wave File Into an Arbitrary Format

When reading sample data you can convert it to a different format on the fly. For example, let’s say my_file.wav has 2 channels and 16-bit PCM samples, but you want 1 channel normalized floating point samples. By passing a Format instance to the constructor, the samples will be read out in the desired format regardless of what is actually in the file:

require "wavefile"
include WaveFile

# Samples will be read as monophonic floating point,
# regardless of the actual sample format in the file
target_format = Format.new(:mono, :float, 44100)
Reader.new("my_file.wav", target_format).each_buffer do |buffer|
  puts "Read #{buffer.samples.length} sample frames."
end

Note: Sample data will not be re-sampled to match the target sample rate. For example, if reading data out of a file with 44,100Hz sample rate using a target Format with a 22,050Hz sample rate, the samples will not be re-sampled to a 22,050Hz sample rate. This means that audio will sound pitch shifted unless the file’s actual sample rate and the target sample rate are the same.

Getting Metadata About a Wave File

Reader#format contains info about the format samples will be converted to when being read out of the file, while Reader#native_format contains info about how the samples are actually stored in the file.

Reader#readable_format? will indicate whether the sample data is in a format understood by the WaveFile gem. (If not, an error will be raised when calling Reader#read).

Reader#duration can be used to determine the playback time of the file.

require "wavefile"
include WaveFile

file_name = ARGV[0]
puts "Metadata for #{file_name}:"

begin
  reader = Reader.new(file_name)

  puts "  Readable by this gem?  #{reader.readable_format? ? 'Yes' : 'No'}"
  puts "  Audio Format:          #{reader.native_format.audio_format}"
  puts "  Channels:              #{reader.native_format.channels}"
  puts "  Bits per sample:       #{reader.native_format.bits_per_sample}"
  puts "  Samples per second:    #{reader.native_format.sample_rate}"
  puts "  Bytes per second:      #{reader.native_format.byte_rate}"
  puts "  Block align:           #{reader.native_format.block_align}"
  puts "  Sample frame count:    #{reader.total_sample_frames}"

  duration = reader.total_duration
  formatted_duration = duration.hours.to_s.rjust(2, "0") << ":" <<
                       duration.minutes.to_s.rjust(2, "0") << ":" <<
                       duration.seconds.to_s.rjust(2, "0") << ":" <<
                       duration.milliseconds.to_s.rjust(3, "0")
  puts "  Play time:             #{formatted_duration}"
rescue InvalidFormatError => error
  puts "  Not a valid Wave file! Error message: \"#{error}\""
end

Writing a Wave File With A Block

The Writer object is used to write data to a wave file. When constructing the Writer, you can pass a block inside which the writing will occur. When the block exits the file will automatically be closed. The example below shows how to write a basic square wave to a file.

require "wavefile"
include WaveFile

# Write a 441Hz square wave beep lasting for 1 second
amplitude = 0.25
square_wave_cycle = ([amplitude] * 50) + ([-amplitude] * 50)
buffer = Buffer.new(square_wave_cycle, Format.new(:mono, :float, 44100))
Writer.new("my_file.wav", Format.new(:mono, :pcm_16, 44100)) do |writer|
  441.times { writer.write(buffer) }
end

This should result in a file named my_file.wav being written to the current directory, and it should sound like this:

For more info on how to create simple sounds with Ruby, check out NanoSynth.

Writing a Wave File (Manual File Close Edition)

Alternately, you can manually control when to close the file, as shown below. Note that a file won’t be valid for playback until it is closed. This is because some data required for playback (such as the total number of samples) isn’t written to the file until it is closed.

require "wavefile"
include WaveFile

writer = Writer.new("my_file.wav", Format.new(:mono, :pcm_16, 44100))

# Write a 441Hz square wave beep lasting for 1 second
amplitude = 0.25
square_wave_cycle = ([amplitude] * 50) + ([-amplitude] * 50)
buffer = Buffer.new(square_wave_cycle, Format.new(:mono, :float, 44100))
441.times do
  writer.write(buffer)
end

# The Wave file isn't in a valid state until this is called
writer.close

This should result in a file identical to the previous example:

Copying a Wave File to Different Format

In this example, the sample data in the file original.wav will be written to copy.wav as stereo/16-bit with a 44,100Hz sample rate, regardless of what format the sample data in original.wav is stored in.

This example also shows that you can also pass a block to Writer.new(), and the Writer will automatically be closed when the block exits.

require "wavefile"
include WaveFile

Writer.new("copy.wav", Format.new(:stereo, :pcm_16, 44100)) do |writer|
  Reader.new("original.wav").each_buffer do |buffer|
    writer.write(buffer)
  end
end

Appending Wave Files

This example will take 3 Wave files, and write them to a single file containing each input file played one after another. Note that the individual files can be in different formats. They will all be converted to the output file’s format.

require "wavefile"
include WaveFile

FILES_TO_APPEND = ["file1.wav", "file2.wav", "file3.wav"]

Writer.new("append.wav", Format.new(:stereo, :pcm_16, 44100)) do |writer|
  FILES_TO_APPEND.each do |file_name|
    Reader.new(file_name).each_buffer do |buffer|
      writer.write(buffer)
    end
  end
end

View the source on GitHub

Copyright © Joel Strait 2009-23