@@ -20,75 +20,60 @@ def call(io)
2020 # with the exception that the Format chunk must precede the Data chunk.
2121 # The specification does not require the Format chunk to be the first chunk
2222 # after the RIFF header.
23- # http://soundfile.sapp.org/doc/WaveFormat/
24- # For WAVE files containing PCM audio format we parse the 'fmt ' and
25- # 'data' chunks while for non PCM audio formats the 'fmt ' and 'fact'
26- # chunks. In the latter case the order fo appearence of the chunks is
27- # arbitrary.
28- fmt_processed = false
29- fact_processed = false
23+ # https://www.mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
3024 fmt_data = { }
31- total_sample_frames = 0
25+ data_size = 0
26+ total_sample_frames = nil
3227 loop do
3328 chunk_type , chunk_size = safe_read ( io , 8 ) . unpack ( 'a4l' )
3429 case chunk_type
3530 when 'fmt ' # watch out: the chunk ID of the format chunk ends with a space
3631 fmt_data = unpack_fmt_chunk ( io , chunk_size )
37- return process_non_pcm ( fmt_data , total_sample_frames ) if fmt_data [ :audio_format ] != 1 and fact_processed
38- fmt_processed = true
3932 when 'data'
40- return unless fmt_processed # the 'data' chunk cannot preceed the 'fmt ' chunk
41- return process_pcm ( fmt_data , chunk_size ) if fmt_data [ :audio_format ] == 1
42- safe_skip ( io , chunk_size )
33+ data_size = chunk_size
4334 when 'fact'
4435 total_sample_frames = safe_read ( io , 4 ) . unpack ( 'l' ) . first
4536 safe_skip ( io , chunk_size - 4 )
46- return process_non_pcm ( fmt_data , total_sample_frames ) if fmt_processed and fmt_data [ :audio_format ] != 1
47- fact_processed = true
4837 else
4938 # Skip this chunk until a known chunk is encountered
5039 safe_skip ( io , chunk_size )
5140 end
41+ rescue FormatParser ::IOUtils ::InvalidRead
42+ # We've reached EOF, so it's time to make the most out of the metadata we
43+ # managed to parse
44+ break
5245 end
46+
47+ file_info ( fmt_data , data_size , total_sample_frames )
5348 end
5449
5550 def unpack_fmt_chunk ( io , chunk_size )
5651 # The size of the fmt chunk is at least 16 bytes. If the format tag's value is not
5752 # 1 compression might be in use for storing the data
5853 # and the fmt chunk might contain extra fields appended to it.
59- # The last 4 fields of the fmt tag are always:
54+ # The first 6 fields of the fmt tag are always:
55+ # * unsigned short audio format
6056 # * unsigned short channels
6157 # * unsigned long samples per sec
6258 # * unsigned long average bytes per sec
6359 # * unsigned short block align
6460 # * unsigned short bits per sample
6561
66- fmt_info = safe_read ( io , 16 ) . unpack ( 'S_2I2S_2' )
62+ _ , channels , sample_rate , byte_rate , _ , bits_per_sample = safe_read ( io , 16 ) . unpack ( 'S_2I2S_2' )
6763 safe_skip ( io , chunk_size - 16 ) # skip the extra fields
6864
6965 {
70- audio_format : fmt_info [ 0 ] ,
71- channels : fmt_info [ 1 ] ,
72- sample_rate : fmt_info [ 2 ] ,
73- byte_rate : fmt_info [ 3 ] ,
74- block_align : fmt_info [ 4 ] ,
75- bits_per_sample : fmt_info [ 5 ] ,
66+ channels : channels ,
67+ sample_rate : sample_rate ,
68+ byte_rate : byte_rate ,
69+ bits_per_sample : bits_per_sample ,
7670 }
7771 end
7872
79- def process_pcm ( fmt_data , data_size )
80- return unless fmt_data [ :channels ] > 0 and fmt_data [ :bits_per_sample ] > 0
81- sample_frames = data_size / ( fmt_data [ :channels ] * fmt_data [ :bits_per_sample ] / 8 )
82- file_info ( fmt_data , sample_frames )
83- end
84-
85- def process_non_pcm ( fmt_data , total_sample_frames )
86- file_info ( fmt_data , total_sample_frames )
87- end
88-
89- def file_info ( fmt_data , sample_frames )
90- return unless fmt_data [ :sample_rate ] > 0
91- duration_in_seconds = sample_frames / fmt_data [ :sample_rate ] . to_f
73+ def file_info ( fmt_data , data_size , sample_frames )
74+ # NOTE: Each sample includes information for each channel
75+ sample_frames ||= data_size / ( fmt_data [ :channels ] * fmt_data [ :bits_per_sample ] / 8 ) if fmt_data [ :channels ] > 0 && fmt_data [ :bits_per_sample ] > 0
76+ duration_in_seconds = sample_frames / fmt_data [ :sample_rate ] . to_f if fmt_data [ :sample_rate ] > 0
9277 FormatParser ::Audio . new (
9378 format : :wav ,
9479 num_audio_channels : fmt_data [ :channels ] ,
0 commit comments