Class: WaveFile::Reader

Inherits:
Object
  • Object
show all
Defined in:
lib/wavefile/reader.rb

Overview

Provides the ability to read sample data out of a wave file, as well as query a wave file about its metadata (e.g. number of channels, sample rate, etc).

When constructing a Reader a block can be given. All data should be read inside this block, and when the block exits the Reader will automatically be closed.

Reader.new("my_file.wav") do |reader|
  # Read sample data here
end

Alternately, if a block isn’t given you should make sure to call close when finished reading.

reader = Reader.new("my_file.wav")
# Read sample data here
reader.close

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file_name, format = nil) ⇒ Reader

Returns a Reader object that is ready to start reading the specified file’s sample data.

file_name - The name of the wave file to read from. format - The format that read sample data should be returned in

(default: the wave file's internal format).

Returns a Reader object that is ready to start reading the specified file’s sample data. Raises Errno::ENOENT if the specified file can’t be found Raises InvalidFormatError if the specified file isn’t a valid wave file



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/wavefile/reader.rb', line 41

def initialize(file_name, format=nil)
  @file_name = file_name
  @file = File.open(file_name, "rb")

  begin
    @riff_reader = ChunkReaders::RiffReader.new(@file, @file_name)
  rescue InvalidFormatError
    raise InvalidFormatError, "'#{@file_name}' does not appear to be a valid Wave file"
  end
  
  @raw_native_format = @riff_reader.native_format
  @total_sample_frames = @riff_reader.data_chunk_reader.sample_frame_count
  @current_sample_frame = 0

  native_sample_format = "#{FORMAT_CODES.invert[native_format.audio_format]}_#{native_format.bits_per_sample}".to_sym

  @readable_format = true
  begin
    @native_format = Format.new(@raw_native_format.channels,
                                native_sample_format,
                                @raw_native_format.sample_rate)
    @pack_code = PACK_CODES[@native_format.sample_format][@native_format.bits_per_sample]
  rescue FormatError
    @readable_format = false
    @pack_code = nil
  end

  @format = (format == nil) ? (@native_format || @raw_native_format) : format

  if block_given?
    begin
      yield(self)
    ensure
      close
    end
  end
end

Instance Attribute Details

#current_sample_frameObject (readonly)

Returns the index of the sample frame which is “cued up” for reading. I.e., the index of the next sample frame that will be read. A sample frame contains a single sample for each channel. So if there are 1,000 sample frames in a stereo file, this means there are 1,000 left-channel samples and 1,000 right-channel samples.



191
192
193
# File 'lib/wavefile/reader.rb', line 191

def current_sample_frame
  @current_sample_frame
end

#file_nameObject (readonly)

Returns the name of the Wave file that is being read



180
181
182
# File 'lib/wavefile/reader.rb', line 180

def file_name
  @file_name
end

#formatObject (readonly)

Returns a Format object describing how sample data is being read from the Wave file (number of channels, sample format and bits per sample, etc). Note that this might be different from the underlying format of the Wave file on disk.



185
186
187
# File 'lib/wavefile/reader.rb', line 185

def format
  @format
end

#total_sample_framesObject (readonly)

Returns the total number of sample frames in the file. A sample frame contains a single sample for each channel. So if there are 1,000 sample frames in a stereo file, this means there are 1,000 left-channel samples and 1,000 right-channel samples.



196
197
198
# File 'lib/wavefile/reader.rb', line 196

def total_sample_frames
  @total_sample_frames
end

Instance Method Details

#closeObject

Closes the Reader. After a Reader is closed, no more sample data can be read from it.

Returns nothing. Raises IOError if the Reader is already closed.



156
157
158
# File 'lib/wavefile/reader.rb', line 156

def close
  @file.close
end

#closed?Boolean

Returns true if the Reader is closed, and false if it is open and available for reading.

Returns:

  • (Boolean)


147
148
149
# File 'lib/wavefile/reader.rb', line 147

def closed?
  @file.closed?
end

#each_buffer(sample_frame_count) ⇒ Object

Reads sample data of the into successive Buffers of the specified size, until there is no more sample data to be read. When all sample data has been read, the Reader is automatically closed. Each Buffer is passed to the given block.

Note that sample_frame_count indicates the number of sample frames to read, not number of samples. A sample frame include one sample for each channel. For example, if sample_frame_count is 1024, then for a stereo file 1024 samples will be read from the left channel, and 1024 samples will be read from the right channel.

sample_frame_count - The number of sample frames to read into each Buffer from each channel. The number

of sample frames read into the final Buffer could be less than this size, if there 
are not enough remaining.

Returns nothing.



94
95
96
97
98
99
100
101
102
# File 'lib/wavefile/reader.rb', line 94

def each_buffer(sample_frame_count)
  begin
    while true do
      yield(read(sample_frame_count))
    end
  rescue EOFError
    close
  end
end

#native_formatObject

Returns a Format object describing the sample format of the Wave file being read. This is not necessarily the format that the sample data will be read as - to determine that, use #format.



168
169
170
# File 'lib/wavefile/reader.rb', line 168

def native_format
  @raw_native_format
end

#read(sample_frame_count) ⇒ Object

Reads the specified number of sample frames from the wave file into a Buffer. Note that the Buffer will have at most sample_frame_count sample frames, but could have less if the file doesn’t have enough remaining.

sample_frame_count - The number of sample frames to read. Note that each sample frame includes a sample for

each channel.

Returns a Buffer containing sample_frame_count sample frames Raises UnsupportedFormatError if file is in a format that can’t be read by this gem Raises EOFError if no samples could be read due to reaching the end of the file



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/wavefile/reader.rb', line 114

def read(sample_frame_count)
  raise UnsupportedFormatError unless @readable_format

  if @current_sample_frame >= @total_sample_frames
    #FIXME: Do something different here, because the end of the file has not actually necessarily been reached
    raise EOFError
  elsif sample_frame_count > sample_frames_remaining
    sample_frame_count = sample_frames_remaining
  end

  samples = @file.sysread(sample_frame_count * @native_format.block_align).unpack(@pack_code)
  @current_sample_frame += sample_frame_count

  if @native_format.bits_per_sample == 24
    # Since the sample data is little endian, the 3 bytes will go from least->most significant
    samples = samples.each_slice(3).map {|least_significant_byte, middle_byte, most_significant_byte|
      # Convert the byte read as "C" to one read as "c"
      most_significant_byte = [most_significant_byte].pack("c").unpack("c").first
      
      (most_significant_byte << 16) | (middle_byte << 8) | least_significant_byte
    }
  end

  if @native_format.channels > 1
    samples = samples.each_slice(@native_format.channels).to_a
  end

  buffer = Buffer.new(samples, @native_format)
  buffer.convert(@format)
end

#readable_format?Boolean

Returns true if this is a valid Wave file and contains sample data that is in a format that this class can read, and returns false if this is a valid Wave file but does not contain a sample format supported by this class.

Returns:

  • (Boolean)


175
176
177
# File 'lib/wavefile/reader.rb', line 175

def readable_format?
  @readable_format
end

#total_durationObject

Returns a Duration instance for the total number of sample frames in the file



161
162
163
# File 'lib/wavefile/reader.rb', line 161

def total_duration
  Duration.new(total_sample_frames, @format.sample_rate)
end