File: reader.rb

package info (click to toggle)
ruby-wavefile 1.1.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,708 kB
  • sloc: ruby: 4,171; makefile: 2
file content (214 lines) | stat: -rw-r--r-- 8,702 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
module WaveFile
  # Public: Error that is raised when trying to read from a Reader instance that has been closed.
  class ReaderClosedError < IOError; end

  # Public: 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
  class Reader
    # Public: Constructs a Reader object that is ready to start reading the specified file's
    # sample data.
    #
    # io_or_file_name - The name of the wave file to read from,
    #                   or an open IO object 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.
    def initialize(io_or_file_name, format=nil)
      if io_or_file_name.is_a?(String)
        @io = File.open(io_or_file_name, "rb")
        @io_source = :file_name
      else
        @io = io_or_file_name
        @io_source = :io
      end

      @closed = false

      riff_reader = ChunkReaders::RiffReader.new(@io, format)
      @data_chunk_reader = riff_reader.data_chunk_reader
      @sample_chunk = riff_reader.sample_chunk

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


    # Public: Starting from the current reading position, reads sample frames into successive Buffers
    # of the specified size, until there are no more sample frames to be read. When the final sample
    # frame has been read the Reader is automatically closed. Each Buffer is passed to the given block.
    #
    # If the Reader is constructed from an open IO, the IO is NOT closed after all sample data is
    # read. However, the Reader will be closed and any attempt to continue to read from it will
    # result in an error.
    #
    # 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.
    #
    # Examples
    #
    #   # sample_frame_count not given, so default buffer size
    #   Reader.new("my_file.wav").each_buffer do |buffer|
    #     puts "#{buffer.samples.length} sample frames read"
    #   end
    #
    #   # Specific sample_frame_count given for each buffer
    #   Reader.new("my_file.wav").each_buffer(1024) do |buffer|
    #     puts "#{buffer.samples.length} sample frames read"
    #   end
    #
    #   # Reading each buffer from an externally created IO
    #   file = File.open("my_file.wav", "rb")
    #   Reader.new(file).each_buffer do |buffer|
    #     puts "#{buffer.samples.length} sample frames read"
    #   end
    #   # Although Reader is closed, file still needs to be manually closed
    #   file.close
    #
    #   reader = Reader.new("my_file.wav")
    #   reader.read(100)
    #   # Reading using `each_buffer` will start at the 101st sample frame:
    #   reader.each_buffer do |buffer|
    #     puts "#{buffer.samples.length} sample frames read"
    #   end
    #   # At this point, the Reader is now closed (even without
    #   # a call to `close()`)
    #
    # Returns nothing. Has side effect of closing the Reader.
    def each_buffer(sample_frame_count=4096)
      begin
        while true do
          yield(read(sample_frame_count))
        end
      rescue EOFError
        close
      end
    end


    # Public: 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 ReaderClosedError if the Reader has been closed.
    #
    # Raises EOFError if no samples could be read due to reaching the end of the file.
    def read(sample_frame_count)
      if @closed
        raise ReaderClosedError
      end

      @data_chunk_reader.read(sample_frame_count)
    end


    # Public: Returns true if the Reader is closed, and false if it is open and available for reading.
    def closed?
      @closed
    end


    # Public: Closes the Reader. If the Reader is already closed, does nothing. After a Reader
    # is closed, no more sample data can be read from it. Note: If the Reader is constructed
    # from an open IO instance (as opposed to a file name), the IO instance will _not_ be closed.
    # You'll have to manually close it yourself. This is intentional, because Reader can't know
    # what you may/may not want to do with the IO instance in the future.
    #
    # Returns nothing.
    def close
      return if @closed

      if @io_source == :file_name
        @io.close
      end

      @closed = true
    end

    # Public: Returns a Duration instance which indicates the playback time of the file.
    def total_duration
      Duration.new(total_sample_frames, @data_chunk_reader.format.sample_rate)
    end

    # Public: Returns an object describing the sample format of the Wave file being read.
    # This returns the data contained in the "fmt " chunk of the Wave file. It will not
    # necessarily match the format that the samples are read out as (for that, see #format).
    def native_format
      @data_chunk_reader.raw_native_format
    end

    # Public: 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 that this gem knows how to read.
    def readable_format?
      @data_chunk_reader.readable_format
    end

    # Public: Returns an object describing how sample data is being read from the Wave file.
    # I.e., number of channels, bits per sample, sample format, etc. If #readable_format? is
    # true, then this will be a Format object. The format the samples are read out as might
    # be different from how the samples are actually stored in the file. Therefore, #format
    # might not match #native_format. If #readable_format? is false, then this will return the
    # same value as #native_format.
    def format
      @data_chunk_reader.format
    end

    # Public: 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.
    def current_sample_frame
      @data_chunk_reader.current_sample_frame
    end

    # Public: 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.
    def total_sample_frames
      @data_chunk_reader.total_sample_frames
    end

    # Public: Returns a SamplerInfo object if the file contains "smpl" chunk, or nil if it doesn't.
    # If present, this will contain information about how the file can be use by a sampler, such as
    # corresponding MIDI note, or loop points.
    def sampler_info
      @sample_chunk
    end
  end
end