class Sound TAG_MAP = { 'RIFF' => :_parse_RIFF, 'fmt ' => :_parse_fmt_, 'data' => :_parse_data, } attr_reader :buffer def initialize(path) data = File.read(path) _parse(data) raise "Unreadable WAV: #{path}" unless @channel_count && @sample_rate && @bits_per_sample && @data buffers = [0].pack('i*') alGenBuffers(1, buffers) @buffer = buffers.unpack('i*')[0] alBufferData( @buffer, _format(@channel_count, @bits_per_sample), @data, @data.size, @sample_rate) end def _parse(data) while data.length != 0 do tag, length, chunk_data, data = _read_chunk(data) f = TAG_MAP[tag] if f then self.send(f, length, chunk_data) else puts "ignoring unknown tag #{tag}" end end end def _read_chunk(data) tag, length, rest = data.unpack('a4Va*') chunk_data, extra_data = rest.unpack("a#{length}a*") raise "not enough data in chunk '#{tag}' (expected #{length}, got #{chunk_data.length})" unless chunk_data.length == length return tag, length, chunk_data, extra_data end def _parse_RIFF(length, data) wave, rest = data.unpack('a4a*') raise "not WAVE" unless wave == 'WAVE' _parse(rest) end def _parse_fmt_(length, data) pcm, channel_count, sample_rate, byte_rate, block_align, bits_per_sample = data.unpack('vvVVvv') raise "not PCM" unless pcm == 1 raise "not mono or stereo" unless channel_count == 1 || channel_count == 2 @channel_count = channel_count @sample_rate = sample_rate @bits_per_sample = bits_per_sample end def _parse_data(length, data) @data = data end def _format(channels, bits) if channels == 1 then case bits when 8 return AL_FORMAT_MONO8 when 16 return AL_FORMAT_MONO16 end elsif channels == 2 then case bits when 8 return AL_FORMAT_STEREO8 when 16 return AL_FORMAT_STEREO16 end end raise "Unsupported channels (#{channels}) / bps (#{bits})" end end