class WaveFile::Buffer

Public: Represents a collection of samples in a certain format (e.g. 16-bit mono Integer PCM). Reader returns sample data contained in Buffers, and Writer expects incoming sample data to be contained in a Buffer as well.

Contains methods to convert the sample data in the buffer to a different format.

Attributes

samples[R]

Public: Returns the sample data contained in the Buffer as an Array. If the Format has 1 channel, the Array will be a flat list of samples. If the Format has 2 or more channels, the Array will include sub arrays for each sample frame, with a sample for each channel.

Examples

samples = mono_buffer.samples
# => [-0.5, 0.3, 0.2, -0.9, ...]

samples = stereo_buffer.samples
# => [[-0.2, 0.5], [0.1, 0.2], [-0.4, 0.7], [0.1, 0.2], ...]

samples = three_channel_buffer.samples
# => [[0.3, 0.5, 0.2], [-0.1, 0.2, -0.9], [0.2, 0.3, -0.4], [0.1, 0.2, -0.8], ...]

Public Class Methods

new(samples, format) click to toggle source

Public: Creates a new Buffer.

samples - An array of samples. If the Format has 1 channel (i.e. is mono), this

should be a flat array of samples such as <code>[0.5, 0.4, -0.3, ...]</code>.
If the Format has 2 or more channels the array should include a sub-array for
each sample frame. For example, <code>[[0.5, 0.2], [0.1, 0.6], [-0.2, 0.4], ...]</code>
for a stereo file.

The individual samples should match the given format:

:pcm_8    - Integer between 0 and 255
:pcm_16   - Integer between -32_768 and 32_767
:pcm_24   - Integer between -8_388_608 and 8_388_607
:pcm_32   - Integer between 2_147_483_648 and 2_147_483_647
:float    - Float between -1.0 and 1.0
:float_32 - Float between -1.0 and 1.0
:float_64 - Float between -1.0 and 1.0

format - A Format instance which describes the sample format of the sample array.

Note that the sample array is not compared with the format to make sure
they match - you are on the honor system to make sure they do. If they
don't match, unexpected things will happen.

Examples

samples = ([0.5] * 50) + ([-0.5] * 50)   # A floating point 440Hz mono square wave
buffer = Buffer.new(samples, Format.new(:mono, :float, 44100)

samples = ([0.5, 0.5] * 50) + ([-0.5, -0.5] * 50)   # A 440Hz stereo square wave
buffer = Buffer.new(samples, Format.new(2, :float, 44100)

samples = ([16000] * 50) + ([-16000] * 50)   # A 16-bit PCM 440Hz mono square wave
buffer = Buffer.new(samples, Format.new(1, :pcm_16, 44100)

Returns a constructed Buffer.

# File lib/wavefile/buffer.rb, line 51
def initialize(samples, format)
  @samples = samples
  @format = format
end

Public Instance Methods

bits_per_sample() click to toggle source

Public: Returns the bits per sample of the buffer's sample data

# File lib/wavefile/buffer.rb, line 115
def bits_per_sample
  @format.bits_per_sample
end
channels() click to toggle source

Public: Returns the number of channels the buffer's sample data has

# File lib/wavefile/buffer.rb, line 109
def channels
  @format.channels
end
convert(new_format) click to toggle source

Public: Creates a new Buffer containing the sample data of this Buffer, but converted to a different format.

new_format - The format that the sample data should be converted to. If the new format

has a different number of channels than the original buffer format, the sample data
will be converted in the following way:

1 -> n: Each mono sample will be duplicated into the new number of channels.

n -> 1: Each sample in each sample frame will be averaged into a single sample.

(n > 2) -> 2: The first two channels will be kept, all other channels discarded.

other: Unsupported, will cause BufferConversionError to be raised.

Examples

new_format = Format.new(:mono, :pcm_16, 44100)
new_buffer = old_buffer.convert(new_format)

Returns a new Buffer; the existing Buffer is unmodified.

Raises BufferConversionError if the Buffer can't be converted to the given format

# File lib/wavefile/buffer.rb, line 80
def convert(new_format)
  new_samples = convert_buffer(@samples.dup, @format, new_format)
  Buffer.new(new_samples, new_format)
end
convert!(new_format) click to toggle source

Public: Converts the sample data contained in the Buffer to a new format. The sample data is converted in place, so the existing Buffer is modified.

new_format - The format that the sample data should be converted to. See Buffer#convert

for how samples will be mapped if the new number of channels differs from
the original number of channels.

Examples

new_format = Format.new(:mono, :pcm_16, 44100)
old_buffer.convert!(new_format)

Returns self.

Raises BufferConversionError if the Buffer can't be converted to the given format

# File lib/wavefile/buffer.rb, line 101
def convert!(new_format)
  @samples = convert_buffer(@samples, @format, new_format)
  @format = new_format
  self
end
sample_rate() click to toggle source

Public: Returns the sample rate of the buffer's sample data

# File lib/wavefile/buffer.rb, line 121
def sample_rate
  @format.sample_rate
end

Private Instance Methods

convert_buffer(samples, old_format, new_format) click to toggle source
# File lib/wavefile/buffer.rb, line 144
def convert_buffer(samples, old_format, new_format)
  if old_format.channels > new_format.channels
    samples = convert_channels(samples, old_format.channels, new_format.channels)
    samples = convert_sample_format(samples, old_format, new_format)
  else
    samples = convert_sample_format(samples, old_format, new_format)
    samples = convert_channels(samples, old_format.channels, new_format.channels)
  end

  samples
end
convert_channels(samples, old_channels, new_channels) click to toggle source
# File lib/wavefile/buffer.rb, line 156
def convert_channels(samples, old_channels, new_channels)
  return samples if old_channels == new_channels

  # The cases of mono -> stereo and vice-versa are handled specially,
  # because those conversion methods are faster than the general methods,
  # and the large majority of wave files are expected to be either mono or stereo.
  if old_channels == 1 && new_channels == 2
    samples.map! {|sample| [sample, sample]}
  elsif old_channels == 2 && new_channels == 1
    samples.map! {|sample| (sample[0] + sample[1]) / 2}
  elsif old_channels == 1 && new_channels >= 2
    samples.map! {|sample| [].fill(sample, 0, new_channels)}
  elsif old_channels >= 2 && new_channels == 1
    samples.map! {|sample| sample.inject(0) {|sub_sample, sum| sum + sub_sample } / old_channels }
  elsif old_channels > 2 && new_channels == 2
    samples.map! {|sample| [sample[0], sample[1]]}
  else
    raise BufferConversionError,
          "Conversion of sample data from #{old_channels} channels to #{new_channels} channels is unsupported"
  end

  samples
end
convert_sample_format(samples, old_format, new_format) click to toggle source
# File lib/wavefile/buffer.rb, line 180
def convert_sample_format(samples, old_format, new_format)
  return samples if old_format.sample_format == :float && new_format.sample_format == :float

  if old_format.sample_format == :pcm && new_format.sample_format == :pcm
    convert_sample_format_pcm_to_pcm(samples, old_format.bits_per_sample, new_format.bits_per_sample)
  elsif old_format.sample_format == :pcm && new_format.sample_format == :float
    convert_sample_format_pcm_to_float(samples, old_format.bits_per_sample, new_format.bits_per_sample)
  elsif old_format.sample_format == :float && new_format.sample_format == :pcm
    convert_sample_format_float_to_pcm(samples, old_format.bits_per_sample, new_format.bits_per_sample)
  end
end
convert_sample_format_float_to_pcm(samples, old_bits_per_sample, new_bits_per_sample) click to toggle source
# File lib/wavefile/buffer.rb, line 222
def convert_sample_format_float_to_pcm(samples, old_bits_per_sample, new_bits_per_sample)
  if new_bits_per_sample == 8
    convert_sample_format_helper(samples) {|sample| (sample * 127.0).round + 128 }
  elsif new_bits_per_sample == 16
    convert_sample_format_helper(samples) {|sample| (sample * 32767.0).round }
  elsif new_bits_per_sample == 24
    convert_sample_format_helper(samples) {|sample| (sample * 8388607.0).round }
  elsif new_bits_per_sample == 32
    convert_sample_format_helper(samples) {|sample| (sample * 2147483647.0).round }
  end
end
convert_sample_format_helper(samples, &converter) click to toggle source
# File lib/wavefile/buffer.rb, line 234
def convert_sample_format_helper(samples, &converter)
  more_than_one_channel = (Array === samples.first)

  if more_than_one_channel
    samples.map! do |sample|
      sample.map!(&converter)
    end
  else
    samples.map!(&converter)
  end
end
convert_sample_format_pcm_to_float(samples, old_bits_per_sample, new_bits_per_sample) click to toggle source
# File lib/wavefile/buffer.rb, line 210
def convert_sample_format_pcm_to_float(samples, old_bits_per_sample, new_bits_per_sample)
  if old_bits_per_sample == 8
    convert_sample_format_helper(samples) {|sample| (sample - 128).to_f / 128.0 }
  elsif old_bits_per_sample == 16
    convert_sample_format_helper(samples) {|sample| sample.to_f / 32768.0 }
  elsif old_bits_per_sample == 24
    convert_sample_format_helper(samples) {|sample| sample.to_f / 8388608.0 }
  elsif old_bits_per_sample == 32
    convert_sample_format_helper(samples) {|sample| sample.to_f / 2147483648.0 }
  end
end
convert_sample_format_pcm_to_pcm(samples, old_bits_per_sample, new_bits_per_sample) click to toggle source
# File lib/wavefile/buffer.rb, line 192
def convert_sample_format_pcm_to_pcm(samples, old_bits_per_sample, new_bits_per_sample)
  return samples if old_bits_per_sample == new_bits_per_sample

  shift_amount = (new_bits_per_sample - old_bits_per_sample).abs

  if old_bits_per_sample == 8
    convert_sample_format_helper(samples) {|sample| (sample - 128) << shift_amount }
  elsif new_bits_per_sample == 8
    convert_sample_format_helper(samples) {|sample| (sample >> shift_amount) + 128 }
  else
    if new_bits_per_sample > old_bits_per_sample
      convert_sample_format_helper(samples) {|sample| sample << shift_amount }
    else
      convert_sample_format_helper(samples) {|sample| sample >> shift_amount }
    end
  end
end