I've been extremely busy lately, but I wanted to give this one a try.
This solution is not complete as far as the problem specification
goes, but my bit o' metaprogramming-type stuff works, though I'd have
liked to push it further.

class ID3

   @@recLen = 0

   def ID3.field(name, len, flags=[])
      class_eval(%Q[
         def #{name}
            @data[#{@@recLen}, #{len}].strip
         end
      ])

      unless flags.include?(:readonly)
         class_eval(%Q[
            def #{name}=(val)
               # need to pad val to len
               @data[#{@@recLen}, #{len}] = val.ljust(#{len}, "\000")
            end
         ])
      end
      @@recLen += len
   end

   # --------------------------------------------------------------
   #     name, length,       flags
   field :sig,      3,  [:readonly]
   field :song,    30
   field :album,   30
   field :artist,  30
   field :year,     4
   field :comment, 30
   field :genre,    1

   TAG_SIG  = "TAG"
   TAG_SIZE = @@recLen
   raise "ID3 tag size not 128!" unless TAG_SIZE == 128

   # --------------------------------------------------------------

   def ID3.createFromBuffer(buffer)
      ID3.new(buffer)
   end

   def ID3.createFromFile(fname)
      size = File.size?(fname)
      raise "Missing or empty file" unless size
      raise "Invalid file" if size < TAG_SIZE

      # Read the tag and pass to createFromBuffer
      open(fname, "rb") do |f|
         f.seek(-TAG_SIZE, IO::SEEK_END)
         createFromBuffer(f.read(TAG_SIZE))
      end
   end

   # --------------------------------------------------------------

   def initialize(data)
      @data = data

      raise "Wrong buffer size" unless @data.size == TAG_SIZE
      raise "ID3 tag not found" unless self.sig == TAG_SIG
   end

end


id = ID3.createFromFile("maple-leaf-rag.mp3")
puts id.song