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
|
module WaveFile
module ChunkReaders
# Internal
class DataChunkReader < BaseChunkReader # :nodoc:
def initialize(io, chunk_size, raw_native_format, format=nil)
@io = io
@raw_native_format = raw_native_format
@total_sample_frames = chunk_size / @raw_native_format.block_align
@current_sample_frame = 0
@readable_format = true
begin
@native_format = @raw_native_format.to_validated_format
@pack_code = PACK_CODES[@native_format.sample_format][@native_format.bits_per_sample]
rescue FormatError
@readable_format = false
@native_format = nil
@pack_code = nil
end
@format = (format == nil) ? (@native_format || @raw_native_format) : format
end
def read(sample_frame_count)
raise UnsupportedFormatError unless @readable_format
if @current_sample_frame >= @total_sample_frames
# The end of the file has not necessarily been reached if there is another chunk after
# the data chunk, but EOFError is raised for backwards compatibility with older versions
# of the gem, and because it is also generally semantically correct that the "relevant"
# end of the file has been reached.
raise EOFError
elsif sample_frame_count > sample_frames_remaining
sample_frame_count = sample_frames_remaining
end
byte_count = sample_frame_count * @native_format.block_align
samples = @io.read(byte_count)
if samples.nil?
raise EOFError
end
samples = samples.unpack(@pack_code)
if @native_format.bits_per_sample == 24
samples = convert_24_bit_samples(samples)
end
if @native_format.channels > 1
samples = samples.each_slice(@native_format.channels).to_a
end
@current_sample_frame += samples.length
buffer = Buffer.new(samples, @native_format)
buffer.convert(@format)
end
attr_reader :raw_native_format,
:format,
:current_sample_frame,
:total_sample_frames,
:readable_format
private
# The number of sample frames in the file after the current sample frame
def sample_frames_remaining
@total_sample_frames - @current_sample_frame
end
# Since Ruby doesn't have a way to natively extract 24-bit values using pack/unpack,
# unsigned bytes are read instead, and then every 3 is manually combined into a
# signed 24-bit integer.
# Since the sample data is little endian, the 3 bytes will go from least->most significant
def convert_24_bit_samples(samples)
samples.each_slice(3).map do |least_significant_byte, middle_byte, most_significant_byte|
# Convert the most significant byte from unsigned to signed, since 24-bit samples are signed
most_significant_byte = [most_significant_byte].pack("c").unpack("c").first
(most_significant_byte << 16) | (middle_byte << 8) | least_significant_byte
end
end
end
end
end
|